-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Expand file tree
/
Copy pathremoveScripts.js
More file actions
130 lines (113 loc) · 3.38 KB
/
Copy pathremoveScripts.js
File metadata and controls
130 lines (113 loc) · 3.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
import { attrsGroups } from './_collections.js';
import { detachNodeFromParent } from '../lib/xast.js';
export const name = 'removeScripts';
export const description = 'removes scripts';
/** Union of all event attributes. */
const eventAttrs = [
...attrsGroups.animationEvent,
...attrsGroups.documentEvent,
...attrsGroups.documentElementEvent,
...attrsGroups.globalEvent,
...attrsGroups.graphicalEvent,
];
/** Namespaces that support executable <script> elements. */
const SCRIPT_NAMESPACES = [
'http://www.w3.org/2000/svg',
'http://www.w3.org/1999/xhtml',
];
/**
* @param {string} elem
* @param {string} targetElem
* @param {ReadonlyMap<string, string[]>} prefixes
* @param {string[]} targetNamespaces
* @returns {boolean}
*/
function isNamespaceAwareElem(elem, targetElem, prefixes, targetNamespaces) {
if (elem === targetElem) {
return true;
}
if (elem.includes(':')) {
const [prefix, effectiveTag] = elem.split(':', 2);
if (targetElem === effectiveTag) {
const namespaces = /** @type {string[]} */ (prefixes.get(prefix));
const namespace = namespaces[namespaces.length - 1];
return targetNamespaces.includes(namespace);
}
}
return false;
}
/**
* Remove scripts.
*
* https://www.w3.org/TR/SVG11/script.html
*
* @author Patrick Klingemann
* @type {import('../lib/types.js').Plugin}
*/
export const fn = () => {
/**
* Map of XML namespace prefixes to the XML namespace. Each value is a stack
* as XML namespaces can be pushed to in children elements and revert back
* previous namespace when we exit that node.
*
* @type {Map<string, string[]>} */
const prefixes = new Map();
return {
element: {
enter: (node, parentNode) => {
for (const [k, v] of Object.entries(node.attributes)) {
if (!k.startsWith('xmlns:')) {
continue;
}
const prefix = k.slice(6);
if (!prefixes.has(prefix)) {
prefixes.set(prefix, [v]);
} else {
/** @type {string[]} */ (prefixes.get(prefix)).push(v);
}
}
if (
isNamespaceAwareElem(node.name, 'script', prefixes, SCRIPT_NAMESPACES)
) {
detachNodeFromParent(node, parentNode);
return;
}
for (const attr of eventAttrs) {
if (node.attributes[attr] != null) {
delete node.attributes[attr];
}
}
},
exit: (node, parentNode) => {
for (const k of Object.keys(node.attributes)) {
if (!k.startsWith('xmlns:')) {
continue;
}
const prefix = k.slice(6);
/** @type {string[]} */ (prefixes.get(prefix)).pop();
}
if (node.name !== 'a') {
return;
}
for (const attr of Object.keys(node.attributes)) {
if (attr === 'href' || attr.endsWith(':href')) {
if (
node.attributes[attr] == null ||
!node.attributes[attr]
.trimStart()
.toLowerCase()
.startsWith('javascript:')
) {
continue;
}
const index = parentNode.children.indexOf(node);
const usefulChildren = node.children.filter(
(child) => child.type !== 'text',
);
parentNode.children.splice(index, 1, ...usefulChildren);
}
}
},
},
};
};