umap-project 2.8.2__py3-none-any.whl → 2.9.0__py3-none-any.whl
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.
Potentially problematic release.
This version of umap-project might be problematic. Click here for more details.
- umap/__init__.py +1 -1
- umap/admin.py +15 -2
- umap/asgi.py +12 -7
- umap/context_processors.py +1 -0
- umap/locale/br/LC_MESSAGES/django.mo +0 -0
- umap/locale/br/LC_MESSAGES/django.po +111 -67
- umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- umap/locale/cs_CZ/LC_MESSAGES/django.po +110 -66
- umap/locale/el/LC_MESSAGES/django.mo +0 -0
- umap/locale/el/LC_MESSAGES/django.po +129 -85
- umap/locale/en/LC_MESSAGES/django.po +103 -60
- umap/locale/es/LC_MESSAGES/django.mo +0 -0
- umap/locale/es/LC_MESSAGES/django.po +114 -69
- umap/locale/fr/LC_MESSAGES/django.mo +0 -0
- umap/locale/fr/LC_MESSAGES/django.po +105 -61
- umap/locale/gl/LC_MESSAGES/django.mo +0 -0
- umap/locale/gl/LC_MESSAGES/django.po +216 -171
- umap/locale/it/LC_MESSAGES/django.mo +0 -0
- umap/locale/it/LC_MESSAGES/django.po +142 -98
- umap/locale/nl/LC_MESSAGES/django.mo +0 -0
- umap/locale/nl/LC_MESSAGES/django.po +196 -151
- umap/locale/pt/LC_MESSAGES/django.mo +0 -0
- umap/locale/pt/LC_MESSAGES/django.po +115 -71
- umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
- umap/locale/zh_TW/LC_MESSAGES/django.po +109 -65
- umap/management/commands/empty_trash.py +12 -1
- umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
- umap/models.py +43 -13
- umap/settings/base.py +5 -2
- umap/static/umap/base.css +5 -2
- umap/static/umap/content.css +2 -22
- umap/static/umap/css/bar.css +39 -10
- umap/static/umap/css/contextmenu.css +14 -2
- umap/static/umap/css/form.css +33 -39
- umap/static/umap/css/icon.css +47 -5
- umap/static/umap/css/panel.css +20 -2
- umap/static/umap/css/popup.css +0 -1
- umap/static/umap/css/tooltip.css +33 -31
- umap/static/umap/img/16-white.svg +5 -3
- umap/static/umap/img/16.svg +1 -1
- umap/static/umap/img/24-white.svg +17 -16
- umap/static/umap/img/24.svg +29 -18
- umap/static/umap/img/providers/bitbucket.png +0 -0
- umap/static/umap/img/providers/github.png +0 -0
- umap/static/umap/img/providers/keycloak.png +0 -0
- umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
- umap/static/umap/img/providers/twitter-oauth2.png +0 -0
- umap/static/umap/img/source/16-white.svg +6 -4
- umap/static/umap/img/source/16.svg +1 -1
- umap/static/umap/img/source/24-white.svg +20 -18
- umap/static/umap/img/source/24.svg +30 -19
- umap/static/umap/js/components/alerts/alert.js +4 -1
- umap/static/umap/js/modules/browser.js +8 -8
- umap/static/umap/js/modules/caption.js +30 -7
- umap/static/umap/js/modules/data/features.js +101 -56
- umap/static/umap/js/modules/data/layer.js +108 -83
- umap/static/umap/js/modules/form/builder.js +242 -0
- umap/static/umap/js/modules/form/fields.js +1346 -0
- umap/static/umap/js/modules/formatter.js +9 -8
- umap/static/umap/js/modules/help.js +20 -24
- umap/static/umap/js/modules/importer.js +6 -3
- umap/static/umap/js/modules/permissions.js +11 -6
- umap/static/umap/js/modules/rendering/icon.js +5 -1
- umap/static/umap/js/modules/rendering/layers/classified.js +12 -8
- umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
- umap/static/umap/js/modules/rendering/map.js +1 -23
- umap/static/umap/js/modules/rendering/ui.js +20 -38
- umap/static/umap/js/modules/rules.js +3 -2
- umap/static/umap/js/modules/saving.js +5 -0
- umap/static/umap/js/modules/schema.js +8 -6
- umap/static/umap/js/modules/share.js +3 -3
- umap/static/umap/js/modules/sync/engine.js +56 -26
- umap/static/umap/js/modules/sync/updaters.js +15 -6
- umap/static/umap/js/modules/sync/websocket.js +50 -37
- umap/static/umap/js/modules/tableeditor.js +3 -2
- umap/static/umap/js/modules/ui/bar.js +101 -9
- umap/static/umap/js/modules/ui/base.js +7 -24
- umap/static/umap/js/modules/ui/contextmenu.js +9 -2
- umap/static/umap/js/modules/ui/panel.js +5 -1
- umap/static/umap/js/modules/ui/tooltip.js +19 -11
- umap/static/umap/js/modules/umap.js +121 -68
- umap/static/umap/js/modules/utils.js +196 -12
- umap/static/umap/js/umap.controls.js +11 -353
- umap/static/umap/locale/am_ET.js +17 -5
- umap/static/umap/locale/am_ET.json +17 -5
- umap/static/umap/locale/ar.js +17 -5
- umap/static/umap/locale/ar.json +17 -5
- umap/static/umap/locale/ast.js +17 -5
- umap/static/umap/locale/ast.json +17 -5
- umap/static/umap/locale/bg.js +17 -5
- umap/static/umap/locale/bg.json +17 -5
- umap/static/umap/locale/br.js +33 -20
- umap/static/umap/locale/br.json +33 -20
- umap/static/umap/locale/ca.js +17 -5
- umap/static/umap/locale/ca.json +17 -5
- umap/static/umap/locale/cs_CZ.js +15 -5
- umap/static/umap/locale/cs_CZ.json +15 -5
- umap/static/umap/locale/da.js +17 -5
- umap/static/umap/locale/da.json +17 -5
- umap/static/umap/locale/de.js +17 -5
- umap/static/umap/locale/de.json +17 -5
- umap/static/umap/locale/el.js +63 -51
- umap/static/umap/locale/el.json +63 -51
- umap/static/umap/locale/en.js +15 -5
- umap/static/umap/locale/en.json +15 -5
- umap/static/umap/locale/en_US.json +17 -5
- umap/static/umap/locale/es.js +25 -13
- umap/static/umap/locale/es.json +25 -13
- umap/static/umap/locale/et.js +17 -5
- umap/static/umap/locale/et.json +17 -5
- umap/static/umap/locale/eu.js +17 -5
- umap/static/umap/locale/eu.json +17 -5
- umap/static/umap/locale/fa_IR.js +17 -5
- umap/static/umap/locale/fa_IR.json +17 -5
- umap/static/umap/locale/fi.js +17 -5
- umap/static/umap/locale/fi.json +17 -5
- umap/static/umap/locale/fr.js +16 -6
- umap/static/umap/locale/fr.json +16 -6
- umap/static/umap/locale/gl.js +357 -345
- umap/static/umap/locale/gl.json +357 -345
- umap/static/umap/locale/he.js +17 -5
- umap/static/umap/locale/he.json +17 -5
- umap/static/umap/locale/hr.js +17 -5
- umap/static/umap/locale/hr.json +17 -5
- umap/static/umap/locale/hu.js +14 -5
- umap/static/umap/locale/hu.json +14 -5
- umap/static/umap/locale/id.js +17 -5
- umap/static/umap/locale/id.json +17 -5
- umap/static/umap/locale/is.js +17 -5
- umap/static/umap/locale/is.json +17 -5
- umap/static/umap/locale/it.js +125 -113
- umap/static/umap/locale/it.json +125 -113
- umap/static/umap/locale/ja.js +17 -5
- umap/static/umap/locale/ja.json +17 -5
- umap/static/umap/locale/ko.js +17 -5
- umap/static/umap/locale/ko.json +17 -5
- umap/static/umap/locale/lt.js +17 -5
- umap/static/umap/locale/lt.json +17 -5
- umap/static/umap/locale/ms.js +17 -5
- umap/static/umap/locale/ms.json +17 -5
- umap/static/umap/locale/nl.js +132 -119
- umap/static/umap/locale/nl.json +132 -119
- umap/static/umap/locale/no.js +17 -5
- umap/static/umap/locale/no.json +17 -5
- umap/static/umap/locale/pl.js +17 -5
- umap/static/umap/locale/pl.json +17 -5
- umap/static/umap/locale/pl_PL.json +17 -5
- umap/static/umap/locale/pt.js +38 -25
- umap/static/umap/locale/pt.json +38 -25
- umap/static/umap/locale/pt_BR.js +17 -5
- umap/static/umap/locale/pt_BR.json +17 -5
- umap/static/umap/locale/pt_PT.js +17 -5
- umap/static/umap/locale/pt_PT.json +17 -5
- umap/static/umap/locale/ro.js +17 -5
- umap/static/umap/locale/ro.json +17 -5
- umap/static/umap/locale/ru.js +17 -5
- umap/static/umap/locale/ru.json +17 -5
- umap/static/umap/locale/sk_SK.js +17 -5
- umap/static/umap/locale/sk_SK.json +17 -5
- umap/static/umap/locale/sl.js +17 -5
- umap/static/umap/locale/sl.json +17 -5
- umap/static/umap/locale/sr.js +17 -5
- umap/static/umap/locale/sr.json +17 -5
- umap/static/umap/locale/sv.js +17 -5
- umap/static/umap/locale/sv.json +17 -5
- umap/static/umap/locale/th_TH.js +17 -5
- umap/static/umap/locale/th_TH.json +17 -5
- umap/static/umap/locale/tr.js +17 -5
- umap/static/umap/locale/tr.json +17 -5
- umap/static/umap/locale/uk_UA.js +17 -5
- umap/static/umap/locale/uk_UA.json +17 -5
- umap/static/umap/locale/vi.js +17 -5
- umap/static/umap/locale/vi.json +17 -5
- umap/static/umap/locale/vi_VN.json +17 -5
- umap/static/umap/locale/zh.js +17 -5
- umap/static/umap/locale/zh.json +17 -5
- umap/static/umap/locale/zh_CN.json +17 -5
- umap/static/umap/locale/zh_TW.Big5.json +17 -5
- umap/static/umap/locale/zh_TW.js +15 -5
- umap/static/umap/locale/zh_TW.json +15 -5
- umap/static/umap/map.css +29 -76
- umap/static/umap/nav.css +6 -3
- umap/static/umap/unittests/utils.js +14 -0
- umap/static/umap/vars.css +3 -0
- umap/static/umap/vendors/dompurify/purify.es.js +138 -354
- umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
- umap/static/umap/vendors/editable/Leaflet.Editable.js +1 -0
- umap/sync/__init__.py +0 -0
- umap/sync/app.py +187 -0
- umap/sync/payloads.py +56 -0
- umap/templates/auth/user_detail.html +4 -0
- umap/templates/auth/user_form.html +9 -6
- umap/templates/auth/user_stars.html +4 -0
- umap/templates/base.html +1 -1
- umap/templates/registration/login.html +2 -5
- umap/templates/umap/about.html +5 -0
- umap/templates/umap/about_summary.html +2 -2
- umap/templates/umap/components/provider.html +8 -0
- umap/templates/umap/content_footer.html +1 -1
- umap/templates/umap/css.html +0 -2
- umap/templates/umap/js.html +0 -4
- umap/templates/umap/map_detail.html +1 -1
- umap/templates/umap/password_change.html +4 -0
- umap/templates/umap/password_change_done.html +4 -0
- umap/templates/umap/search.html +4 -0
- umap/templates/umap/search_bar.html +1 -0
- umap/templates/umap/team_confirm_delete.html +4 -0
- umap/templates/umap/team_detail.html +4 -0
- umap/templates/umap/team_form.html +4 -0
- umap/templates/umap/user_dashboard.html +1 -1
- umap/templates/umap/user_teams.html +4 -0
- umap/tests/base.py +3 -1
- umap/tests/integration/conftest.py +16 -23
- umap/tests/integration/test_anonymous_owned_map.py +2 -2
- umap/tests/integration/test_basics.py +4 -7
- umap/tests/integration/test_caption.py +1 -0
- umap/tests/integration/test_categorized_layer.py +4 -8
- umap/tests/integration/test_choropleth.py +1 -1
- umap/tests/integration/test_conditional_rules.py +3 -3
- umap/tests/integration/test_draw_polygon.py +14 -22
- umap/tests/integration/test_draw_polyline.py +6 -14
- umap/tests/integration/test_edit_datalayer.py +11 -11
- umap/tests/integration/test_edit_map.py +30 -4
- umap/tests/integration/test_edit_marker.py +5 -5
- umap/tests/integration/test_edit_polygon.py +6 -6
- umap/tests/integration/test_features_id_generation.py +2 -6
- umap/tests/integration/test_import.py +115 -29
- umap/tests/integration/test_optimistic_merge.py +1 -0
- umap/tests/integration/test_owned_map.py +1 -1
- umap/tests/integration/test_picto.py +8 -8
- umap/tests/integration/test_save.py +3 -2
- umap/tests/integration/test_star.py +13 -9
- umap/tests/integration/test_tableeditor.py +8 -7
- umap/tests/integration/test_view_marker.py +10 -0
- umap/tests/integration/test_websocket_sync.py +239 -64
- umap/tests/settings.py +2 -0
- umap/tests/test_datalayer.py +2 -3
- umap/tests/test_datalayer_views.py +20 -1
- umap/tests/test_empty_trash.py +10 -3
- umap/tests/test_map_views.py +11 -0
- umap/utils.py +27 -11
- umap/views.py +37 -6
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/METADATA +22 -22
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/RECORD +247 -248
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/WHEEL +1 -1
- umap/management/commands/run_websocket_server.py +0 -23
- umap/settings/local_s3.py +0 -45
- umap/static/umap/bitbucket.png +0 -0
- umap/static/umap/github.png +0 -0
- umap/static/umap/js/umap.forms.js +0 -1242
- umap/static/umap/keycloak.png +0 -0
- umap/static/umap/openstreetmap.png +0 -0
- umap/static/umap/twitter.png +0 -0
- umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
- umap/static/umap/vendors/toolbar/leaflet.toolbar.css +0 -1
- umap/static/umap/vendors/toolbar/leaflet.toolbar.js +0 -1
- umap/tests/test_websocket_server.py +0 -22
- umap/websocket_server.py +0 -202
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/entry_points.txt +0 -0
- {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @license DOMPurify 3.
|
|
1
|
+
/*! @license DOMPurify 3.2.4 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.4/LICENSE */
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
entries,
|
|
@@ -37,8 +37,10 @@ if (!construct) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
const arrayForEach = unapply(Array.prototype.forEach);
|
|
40
|
+
const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
40
41
|
const arrayPop = unapply(Array.prototype.pop);
|
|
41
42
|
const arrayPush = unapply(Array.prototype.push);
|
|
43
|
+
const arraySplice = unapply(Array.prototype.splice);
|
|
42
44
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
43
45
|
const stringToString = unapply(String.prototype.toString);
|
|
44
46
|
const stringMatch = unapply(String.prototype.match);
|
|
@@ -48,12 +50,11 @@ const stringTrim = unapply(String.prototype.trim);
|
|
|
48
50
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
49
51
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
50
52
|
const typeErrorCreate = unconstruct(TypeError);
|
|
51
|
-
|
|
52
53
|
/**
|
|
53
54
|
* Creates a new function that calls the given function with a specified thisArg and arguments.
|
|
54
55
|
*
|
|
55
|
-
* @param
|
|
56
|
-
* @returns
|
|
56
|
+
* @param func - The function to be wrapped and called.
|
|
57
|
+
* @returns A new function that calls the given function with a specified thisArg and arguments.
|
|
57
58
|
*/
|
|
58
59
|
function unapply(func) {
|
|
59
60
|
return function (thisArg) {
|
|
@@ -63,12 +64,11 @@ function unapply(func) {
|
|
|
63
64
|
return apply(func, thisArg, args);
|
|
64
65
|
};
|
|
65
66
|
}
|
|
66
|
-
|
|
67
67
|
/**
|
|
68
68
|
* Creates a new function that constructs an instance of the given constructor function with the provided arguments.
|
|
69
69
|
*
|
|
70
|
-
* @param
|
|
71
|
-
* @returns
|
|
70
|
+
* @param func - The constructor function to be wrapped and called.
|
|
71
|
+
* @returns A new function that constructs an instance of the given constructor function with the provided arguments.
|
|
72
72
|
*/
|
|
73
73
|
function unconstruct(func) {
|
|
74
74
|
return function () {
|
|
@@ -78,14 +78,13 @@ function unconstruct(func) {
|
|
|
78
78
|
return construct(func, args);
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
|
-
|
|
82
81
|
/**
|
|
83
82
|
* Add properties to a lookup table
|
|
84
83
|
*
|
|
85
|
-
* @param
|
|
86
|
-
* @param
|
|
87
|
-
* @param
|
|
88
|
-
* @returns
|
|
84
|
+
* @param set - The set to which elements will be added.
|
|
85
|
+
* @param array - The array containing elements to be added to the set.
|
|
86
|
+
* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
|
|
87
|
+
* @returns The modified set with added elements.
|
|
89
88
|
*/
|
|
90
89
|
function addToSet(set, array) {
|
|
91
90
|
let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
|
|
@@ -112,12 +111,11 @@ function addToSet(set, array) {
|
|
|
112
111
|
}
|
|
113
112
|
return set;
|
|
114
113
|
}
|
|
115
|
-
|
|
116
114
|
/**
|
|
117
115
|
* Clean up an array to harden against CSPP
|
|
118
116
|
*
|
|
119
|
-
* @param
|
|
120
|
-
* @returns
|
|
117
|
+
* @param array - The array to be cleaned.
|
|
118
|
+
* @returns The cleaned version of the array
|
|
121
119
|
*/
|
|
122
120
|
function cleanArray(array) {
|
|
123
121
|
for (let index = 0; index < array.length; index++) {
|
|
@@ -128,12 +126,11 @@ function cleanArray(array) {
|
|
|
128
126
|
}
|
|
129
127
|
return array;
|
|
130
128
|
}
|
|
131
|
-
|
|
132
129
|
/**
|
|
133
130
|
* Shallow clone an object
|
|
134
131
|
*
|
|
135
|
-
* @param
|
|
136
|
-
* @returns
|
|
132
|
+
* @param object - The object to be cloned.
|
|
133
|
+
* @returns A new object that copies the original.
|
|
137
134
|
*/
|
|
138
135
|
function clone(object) {
|
|
139
136
|
const newObject = create(null);
|
|
@@ -151,13 +148,12 @@ function clone(object) {
|
|
|
151
148
|
}
|
|
152
149
|
return newObject;
|
|
153
150
|
}
|
|
154
|
-
|
|
155
151
|
/**
|
|
156
152
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
157
153
|
*
|
|
158
|
-
* @param
|
|
159
|
-
* @param
|
|
160
|
-
* @returns
|
|
154
|
+
* @param object - The object to look up the getter function in its prototype chain.
|
|
155
|
+
* @param prop - The property name for which to find the getter function.
|
|
156
|
+
* @returns The getter function found in the prototype chain or a fallback function.
|
|
161
157
|
*/
|
|
162
158
|
function lookupGetter(object, prop) {
|
|
163
159
|
while (object !== null) {
|
|
@@ -179,18 +175,14 @@ function lookupGetter(object, prop) {
|
|
|
179
175
|
}
|
|
180
176
|
|
|
181
177
|
const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
|
|
182
|
-
|
|
183
|
-
// SVG
|
|
184
178
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
185
179
|
const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
|
|
186
|
-
|
|
187
180
|
// List of SVG elements that are disallowed by default.
|
|
188
181
|
// We still need to know them so that we can do namespace
|
|
189
182
|
// checks properly in case one wants to add them to
|
|
190
183
|
// allow-list.
|
|
191
184
|
const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
|
|
192
185
|
const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
|
|
193
|
-
|
|
194
186
|
// Similarly to SVG, we want to know all MathML elements,
|
|
195
187
|
// even those that we disallow by default.
|
|
196
188
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
@@ -204,8 +196,8 @@ const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:x
|
|
|
204
196
|
// eslint-disable-next-line unicorn/better-regex
|
|
205
197
|
const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
|
|
206
198
|
const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
|
|
207
|
-
const TMPLIT_EXPR = seal(
|
|
208
|
-
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]
|
|
199
|
+
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
200
|
+
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
209
201
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
210
202
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
211
203
|
);
|
|
@@ -217,18 +209,19 @@ const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
|
217
209
|
|
|
218
210
|
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
219
211
|
__proto__: null,
|
|
220
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
221
|
-
ERB_EXPR: ERB_EXPR,
|
|
222
|
-
TMPLIT_EXPR: TMPLIT_EXPR,
|
|
223
|
-
DATA_ATTR: DATA_ATTR,
|
|
224
212
|
ARIA_ATTR: ARIA_ATTR,
|
|
225
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
226
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
227
213
|
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
214
|
+
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
215
|
+
DATA_ATTR: DATA_ATTR,
|
|
228
216
|
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
229
|
-
|
|
217
|
+
ERB_EXPR: ERB_EXPR,
|
|
218
|
+
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
219
|
+
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
220
|
+
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
221
|
+
TMPLIT_EXPR: TMPLIT_EXPR
|
|
230
222
|
});
|
|
231
223
|
|
|
224
|
+
/* eslint-disable @typescript-eslint/indent */
|
|
232
225
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
233
226
|
const NODE_TYPE = {
|
|
234
227
|
element: 1,
|
|
@@ -249,20 +242,18 @@ const NODE_TYPE = {
|
|
|
249
242
|
const getGlobal = function getGlobal() {
|
|
250
243
|
return typeof window === 'undefined' ? null : window;
|
|
251
244
|
};
|
|
252
|
-
|
|
253
245
|
/**
|
|
254
246
|
* Creates a no-op policy for internal use only.
|
|
255
247
|
* Don't export this function outside this module!
|
|
256
|
-
* @param
|
|
257
|
-
* @param
|
|
258
|
-
* @return
|
|
248
|
+
* @param trustedTypes The policy factory.
|
|
249
|
+
* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
|
|
250
|
+
* @return The policy created (or null, if Trusted Types
|
|
259
251
|
* are not supported or creating the policy failed).
|
|
260
252
|
*/
|
|
261
253
|
const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
|
|
262
254
|
if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
|
|
263
255
|
return null;
|
|
264
256
|
}
|
|
265
|
-
|
|
266
257
|
// Allow the callers to control the unique policy name
|
|
267
258
|
// by adding a data-tt-policy-suffix to the script element with the DOMPurify.
|
|
268
259
|
// Policy creation with duplicate names throws in Trusted Types.
|
|
@@ -289,22 +280,25 @@ const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedType
|
|
|
289
280
|
return null;
|
|
290
281
|
}
|
|
291
282
|
};
|
|
283
|
+
const _createHooksMap = function _createHooksMap() {
|
|
284
|
+
return {
|
|
285
|
+
afterSanitizeAttributes: [],
|
|
286
|
+
afterSanitizeElements: [],
|
|
287
|
+
afterSanitizeShadowDOM: [],
|
|
288
|
+
beforeSanitizeAttributes: [],
|
|
289
|
+
beforeSanitizeElements: [],
|
|
290
|
+
beforeSanitizeShadowDOM: [],
|
|
291
|
+
uponSanitizeAttribute: [],
|
|
292
|
+
uponSanitizeElement: [],
|
|
293
|
+
uponSanitizeShadowNode: []
|
|
294
|
+
};
|
|
295
|
+
};
|
|
292
296
|
function createDOMPurify() {
|
|
293
297
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
294
298
|
const DOMPurify = root => createDOMPurify(root);
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Version label, exposed for easier checks
|
|
298
|
-
* if DOMPurify is up to date or not
|
|
299
|
-
*/
|
|
300
|
-
DOMPurify.version = '3.1.7';
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Array of elements that DOMPurify removed during sanitation.
|
|
304
|
-
* Empty if nothing was removed.
|
|
305
|
-
*/
|
|
299
|
+
DOMPurify.version = '3.2.4';
|
|
306
300
|
DOMPurify.removed = [];
|
|
307
|
-
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
|
|
301
|
+
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
308
302
|
// Not running in a browser, provide a factory function
|
|
309
303
|
// so that you can pass your own Window
|
|
310
304
|
DOMPurify.isSupported = false;
|
|
@@ -332,7 +326,6 @@ function createDOMPurify() {
|
|
|
332
326
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
333
327
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
334
328
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
335
|
-
|
|
336
329
|
// As per issue #47, the web-components registry is inherited by a
|
|
337
330
|
// new document created via createHTMLDocument. As per the spec
|
|
338
331
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -356,8 +349,7 @@ function createDOMPurify() {
|
|
|
356
349
|
const {
|
|
357
350
|
importNode
|
|
358
351
|
} = originalDocument;
|
|
359
|
-
let hooks =
|
|
360
|
-
|
|
352
|
+
let hooks = _createHooksMap();
|
|
361
353
|
/**
|
|
362
354
|
* Expose whether this browser supports running the full DOMPurify.
|
|
363
355
|
*/
|
|
@@ -375,22 +367,18 @@ function createDOMPurify() {
|
|
|
375
367
|
let {
|
|
376
368
|
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
377
369
|
} = EXPRESSIONS;
|
|
378
|
-
|
|
379
370
|
/**
|
|
380
371
|
* We consider the elements and attributes below to be safe. Ideally
|
|
381
372
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
382
373
|
*/
|
|
383
|
-
|
|
384
374
|
/* allowed element names */
|
|
385
375
|
let ALLOWED_TAGS = null;
|
|
386
376
|
const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);
|
|
387
|
-
|
|
388
377
|
/* Allowed attribute names */
|
|
389
378
|
let ALLOWED_ATTR = null;
|
|
390
379
|
const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
|
|
391
|
-
|
|
392
380
|
/*
|
|
393
|
-
* Configure how
|
|
381
|
+
* Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
|
|
394
382
|
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
|
|
395
383
|
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
|
|
396
384
|
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
|
|
@@ -415,65 +403,49 @@ function createDOMPurify() {
|
|
|
415
403
|
value: false
|
|
416
404
|
}
|
|
417
405
|
}));
|
|
418
|
-
|
|
419
406
|
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
|
|
420
407
|
let FORBID_TAGS = null;
|
|
421
|
-
|
|
422
408
|
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
|
|
423
409
|
let FORBID_ATTR = null;
|
|
424
|
-
|
|
425
410
|
/* Decide if ARIA attributes are okay */
|
|
426
411
|
let ALLOW_ARIA_ATTR = true;
|
|
427
|
-
|
|
428
412
|
/* Decide if custom data attributes are okay */
|
|
429
413
|
let ALLOW_DATA_ATTR = true;
|
|
430
|
-
|
|
431
414
|
/* Decide if unknown protocols are okay */
|
|
432
415
|
let ALLOW_UNKNOWN_PROTOCOLS = false;
|
|
433
|
-
|
|
434
416
|
/* Decide if self-closing tags in attributes are allowed.
|
|
435
417
|
* Usually removed due to a mXSS issue in jQuery 3.0 */
|
|
436
418
|
let ALLOW_SELF_CLOSE_IN_ATTR = true;
|
|
437
|
-
|
|
438
419
|
/* Output should be safe for common template engines.
|
|
439
420
|
* This means, DOMPurify removes data attributes, mustaches and ERB
|
|
440
421
|
*/
|
|
441
422
|
let SAFE_FOR_TEMPLATES = false;
|
|
442
|
-
|
|
443
423
|
/* Output should be safe even for XML used within HTML and alike.
|
|
444
424
|
* This means, DOMPurify removes comments when containing risky content.
|
|
445
425
|
*/
|
|
446
426
|
let SAFE_FOR_XML = true;
|
|
447
|
-
|
|
448
427
|
/* Decide if document with <html>... should be returned */
|
|
449
428
|
let WHOLE_DOCUMENT = false;
|
|
450
|
-
|
|
451
429
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
452
430
|
let SET_CONFIG = false;
|
|
453
|
-
|
|
454
431
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
455
432
|
* document.body. By default, browsers might move them to document.head */
|
|
456
433
|
let FORCE_BODY = false;
|
|
457
|
-
|
|
458
434
|
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
|
|
459
435
|
* string (or a TrustedHTML object if Trusted Types are supported).
|
|
460
436
|
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
|
|
461
437
|
*/
|
|
462
438
|
let RETURN_DOM = false;
|
|
463
|
-
|
|
464
439
|
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html
|
|
465
440
|
* string (or a TrustedHTML object if Trusted Types are supported) */
|
|
466
441
|
let RETURN_DOM_FRAGMENT = false;
|
|
467
|
-
|
|
468
442
|
/* Try to return a Trusted Type object instead of a string, return a string in
|
|
469
443
|
* case Trusted Types are not supported */
|
|
470
444
|
let RETURN_TRUSTED_TYPE = false;
|
|
471
|
-
|
|
472
445
|
/* Output should be free from DOM clobbering attacks?
|
|
473
446
|
* This sanitizes markups named with colliding, clobberable built-in DOM APIs.
|
|
474
447
|
*/
|
|
475
448
|
let SANITIZE_DOM = true;
|
|
476
|
-
|
|
477
449
|
/* Achieve full DOM Clobbering protection by isolating the namespace of named
|
|
478
450
|
* properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
|
|
479
451
|
*
|
|
@@ -489,25 +461,19 @@ function createDOMPurify() {
|
|
|
489
461
|
*/
|
|
490
462
|
let SANITIZE_NAMED_PROPS = false;
|
|
491
463
|
const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
|
|
492
|
-
|
|
493
464
|
/* Keep element content when removing element? */
|
|
494
465
|
let KEEP_CONTENT = true;
|
|
495
|
-
|
|
496
466
|
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
|
|
497
467
|
* of importing it into a new Document and returning a sanitized copy */
|
|
498
468
|
let IN_PLACE = false;
|
|
499
|
-
|
|
500
469
|
/* Allow usage of profiles like html, svg and mathMl */
|
|
501
470
|
let USE_PROFILES = {};
|
|
502
|
-
|
|
503
471
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
504
472
|
let FORBID_CONTENTS = null;
|
|
505
473
|
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
506
|
-
|
|
507
474
|
/* Tags that are safe for data: URIs */
|
|
508
475
|
let DATA_URI_TAGS = null;
|
|
509
476
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
510
|
-
|
|
511
477
|
/* Attributes safe for values like "javascript:" */
|
|
512
478
|
let URI_SAFE_ATTRIBUTES = null;
|
|
513
479
|
const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
|
|
@@ -517,32 +483,33 @@ function createDOMPurify() {
|
|
|
517
483
|
/* Document namespace */
|
|
518
484
|
let NAMESPACE = HTML_NAMESPACE;
|
|
519
485
|
let IS_EMPTY_INPUT = false;
|
|
520
|
-
|
|
521
486
|
/* Allowed XHTML+XML namespaces */
|
|
522
487
|
let ALLOWED_NAMESPACES = null;
|
|
523
488
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
524
|
-
|
|
489
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
490
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
|
|
491
|
+
// Certain elements are allowed in both SVG and HTML
|
|
492
|
+
// namespace. We need to specify them explicitly
|
|
493
|
+
// so that they don't get erroneously deleted from
|
|
494
|
+
// HTML namespace.
|
|
495
|
+
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
525
496
|
/* Parsing of strict XHTML documents */
|
|
526
497
|
let PARSER_MEDIA_TYPE = null;
|
|
527
498
|
const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
|
|
528
499
|
const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
|
|
529
500
|
let transformCaseFunc = null;
|
|
530
|
-
|
|
531
501
|
/* Keep a reference to config to pass to hooks */
|
|
532
502
|
let CONFIG = null;
|
|
533
|
-
|
|
534
503
|
/* Ideally, do not touch anything below this line */
|
|
535
504
|
/* ______________________________________________ */
|
|
536
|
-
|
|
537
505
|
const formElement = document.createElement('form');
|
|
538
506
|
const isRegexOrFunction = function isRegexOrFunction(testValue) {
|
|
539
507
|
return testValue instanceof RegExp || testValue instanceof Function;
|
|
540
508
|
};
|
|
541
|
-
|
|
542
509
|
/**
|
|
543
510
|
* _parseConfig
|
|
544
511
|
*
|
|
545
|
-
* @param
|
|
512
|
+
* @param cfg optional config literal
|
|
546
513
|
*/
|
|
547
514
|
// eslint-disable-next-line complexity
|
|
548
515
|
const _parseConfig = function _parseConfig() {
|
|
@@ -550,39 +517,23 @@ function createDOMPurify() {
|
|
|
550
517
|
if (CONFIG && CONFIG === cfg) {
|
|
551
518
|
return;
|
|
552
519
|
}
|
|
553
|
-
|
|
554
520
|
/* Shield configuration object from tampering */
|
|
555
521
|
if (!cfg || typeof cfg !== 'object') {
|
|
556
522
|
cfg = {};
|
|
557
523
|
}
|
|
558
|
-
|
|
559
524
|
/* Shield configuration object from prototype pollution */
|
|
560
525
|
cfg = clone(cfg);
|
|
561
526
|
PARSER_MEDIA_TYPE =
|
|
562
527
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
563
528
|
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
|
|
564
|
-
|
|
565
529
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
566
530
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
567
|
-
|
|
568
531
|
/* Set configuration parameters */
|
|
569
532
|
ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
|
|
570
533
|
ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
|
|
571
534
|
ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
|
|
572
|
-
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
|
|
573
|
-
|
|
574
|
-
cfg.ADD_URI_SAFE_ATTR,
|
|
575
|
-
// eslint-disable-line indent
|
|
576
|
-
transformCaseFunc // eslint-disable-line indent
|
|
577
|
-
) // eslint-disable-line indent
|
|
578
|
-
: DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
579
|
-
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
|
|
580
|
-
// eslint-disable-line indent
|
|
581
|
-
cfg.ADD_DATA_URI_TAGS,
|
|
582
|
-
// eslint-disable-line indent
|
|
583
|
-
transformCaseFunc // eslint-disable-line indent
|
|
584
|
-
) // eslint-disable-line indent
|
|
585
|
-
: DEFAULT_DATA_URI_TAGS;
|
|
535
|
+
URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
|
|
536
|
+
DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
|
|
586
537
|
FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
|
|
587
538
|
FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
|
|
588
539
|
FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
|
|
@@ -604,6 +555,8 @@ function createDOMPurify() {
|
|
|
604
555
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
605
556
|
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
|
|
606
557
|
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
|
|
558
|
+
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
|
|
559
|
+
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
|
|
607
560
|
CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
|
|
608
561
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
|
|
609
562
|
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
|
|
@@ -620,7 +573,6 @@ function createDOMPurify() {
|
|
|
620
573
|
if (RETURN_DOM_FRAGMENT) {
|
|
621
574
|
RETURN_DOM = true;
|
|
622
575
|
}
|
|
623
|
-
|
|
624
576
|
/* Parse profile info */
|
|
625
577
|
if (USE_PROFILES) {
|
|
626
578
|
ALLOWED_TAGS = addToSet({}, text);
|
|
@@ -645,7 +597,6 @@ function createDOMPurify() {
|
|
|
645
597
|
addToSet(ALLOWED_ATTR, xml);
|
|
646
598
|
}
|
|
647
599
|
}
|
|
648
|
-
|
|
649
600
|
/* Merge configuration parameters */
|
|
650
601
|
if (cfg.ADD_TAGS) {
|
|
651
602
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
@@ -668,17 +619,14 @@ function createDOMPurify() {
|
|
|
668
619
|
}
|
|
669
620
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
670
621
|
}
|
|
671
|
-
|
|
672
622
|
/* Add #text in case KEEP_CONTENT is set to true */
|
|
673
623
|
if (KEEP_CONTENT) {
|
|
674
624
|
ALLOWED_TAGS['#text'] = true;
|
|
675
625
|
}
|
|
676
|
-
|
|
677
626
|
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
|
|
678
627
|
if (WHOLE_DOCUMENT) {
|
|
679
628
|
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
|
|
680
629
|
}
|
|
681
|
-
|
|
682
630
|
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
|
|
683
631
|
if (ALLOWED_TAGS.table) {
|
|
684
632
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
@@ -691,10 +639,8 @@ function createDOMPurify() {
|
|
|
691
639
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
692
640
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
693
641
|
}
|
|
694
|
-
|
|
695
642
|
// Overwrite existing TrustedTypes policy.
|
|
696
643
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
697
|
-
|
|
698
644
|
// Sign local variables required by `sanitize`.
|
|
699
645
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
700
646
|
} else {
|
|
@@ -702,13 +648,11 @@ function createDOMPurify() {
|
|
|
702
648
|
if (trustedTypesPolicy === undefined) {
|
|
703
649
|
trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
704
650
|
}
|
|
705
|
-
|
|
706
651
|
// If creating the internal policy succeeded sign internal variables.
|
|
707
652
|
if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
|
|
708
653
|
emptyHTML = trustedTypesPolicy.createHTML('');
|
|
709
654
|
}
|
|
710
655
|
}
|
|
711
|
-
|
|
712
656
|
// Prevent further manipulation of configuration.
|
|
713
657
|
// Not available in IE8, Safari 5, etc.
|
|
714
658
|
if (freeze) {
|
|
@@ -716,30 +660,19 @@ function createDOMPurify() {
|
|
|
716
660
|
}
|
|
717
661
|
CONFIG = cfg;
|
|
718
662
|
};
|
|
719
|
-
const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
720
|
-
const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
|
|
721
|
-
|
|
722
|
-
// Certain elements are allowed in both SVG and HTML
|
|
723
|
-
// namespace. We need to specify them explicitly
|
|
724
|
-
// so that they don't get erroneously deleted from
|
|
725
|
-
// HTML namespace.
|
|
726
|
-
const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
|
|
727
|
-
|
|
728
663
|
/* Keep track of all possible SVG and MathML tags
|
|
729
664
|
* so that we can perform the namespace checks
|
|
730
665
|
* correctly. */
|
|
731
666
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
732
667
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
733
|
-
|
|
734
668
|
/**
|
|
735
|
-
* @param
|
|
736
|
-
* @returns
|
|
669
|
+
* @param element a DOM element whose namespace is being checked
|
|
670
|
+
* @returns Return false if the element has a
|
|
737
671
|
* namespace that a spec-compliant parser would never
|
|
738
672
|
* return. Return true otherwise.
|
|
739
673
|
*/
|
|
740
674
|
const _checkValidNamespace = function _checkValidNamespace(element) {
|
|
741
675
|
let parent = getParentNode(element);
|
|
742
|
-
|
|
743
676
|
// In JSDOM, if we're inside shadow DOM, then parentNode
|
|
744
677
|
// can be null. We just simulate parent in this case.
|
|
745
678
|
if (!parent || !parent.tagName) {
|
|
@@ -760,14 +693,12 @@ function createDOMPurify() {
|
|
|
760
693
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
761
694
|
return tagName === 'svg';
|
|
762
695
|
}
|
|
763
|
-
|
|
764
696
|
// The only way to switch from MathML to SVG is via`
|
|
765
697
|
// svg if parent is either <annotation-xml> or MathML
|
|
766
698
|
// text integration points.
|
|
767
699
|
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
768
700
|
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
769
701
|
}
|
|
770
|
-
|
|
771
702
|
// We only allow elements that are defined in SVG
|
|
772
703
|
// spec. All others are disallowed in SVG namespace.
|
|
773
704
|
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
@@ -779,13 +710,11 @@ function createDOMPurify() {
|
|
|
779
710
|
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
780
711
|
return tagName === 'math';
|
|
781
712
|
}
|
|
782
|
-
|
|
783
713
|
// The only way to switch from SVG to MathML is via
|
|
784
714
|
// <math> and HTML integration points
|
|
785
715
|
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
786
716
|
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
787
717
|
}
|
|
788
|
-
|
|
789
718
|
// We only allow elements that are defined in MathML
|
|
790
719
|
// spec. All others are disallowed in MathML namespace.
|
|
791
720
|
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
@@ -800,28 +729,24 @@ function createDOMPurify() {
|
|
|
800
729
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
801
730
|
return false;
|
|
802
731
|
}
|
|
803
|
-
|
|
804
732
|
// We disallow tags that are specific for MathML
|
|
805
733
|
// or SVG and should never appear in HTML namespace
|
|
806
734
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
807
735
|
}
|
|
808
|
-
|
|
809
736
|
// For XHTML and XML documents that support custom namespaces
|
|
810
737
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
811
738
|
return true;
|
|
812
739
|
}
|
|
813
|
-
|
|
814
740
|
// The code should never reach this place (this means
|
|
815
741
|
// that the element somehow got namespace that is not
|
|
816
742
|
// HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
|
|
817
743
|
// Return false just in case.
|
|
818
744
|
return false;
|
|
819
745
|
};
|
|
820
|
-
|
|
821
746
|
/**
|
|
822
747
|
* _forceRemove
|
|
823
748
|
*
|
|
824
|
-
* @param
|
|
749
|
+
* @param node a DOM node
|
|
825
750
|
*/
|
|
826
751
|
const _forceRemove = function _forceRemove(node) {
|
|
827
752
|
arrayPush(DOMPurify.removed, {
|
|
@@ -834,46 +759,43 @@ function createDOMPurify() {
|
|
|
834
759
|
remove(node);
|
|
835
760
|
}
|
|
836
761
|
};
|
|
837
|
-
|
|
838
762
|
/**
|
|
839
763
|
* _removeAttribute
|
|
840
764
|
*
|
|
841
|
-
* @param
|
|
842
|
-
* @param
|
|
765
|
+
* @param name an Attribute name
|
|
766
|
+
* @param element a DOM node
|
|
843
767
|
*/
|
|
844
|
-
const _removeAttribute = function _removeAttribute(name,
|
|
768
|
+
const _removeAttribute = function _removeAttribute(name, element) {
|
|
845
769
|
try {
|
|
846
770
|
arrayPush(DOMPurify.removed, {
|
|
847
|
-
attribute:
|
|
848
|
-
from:
|
|
771
|
+
attribute: element.getAttributeNode(name),
|
|
772
|
+
from: element
|
|
849
773
|
});
|
|
850
774
|
} catch (_) {
|
|
851
775
|
arrayPush(DOMPurify.removed, {
|
|
852
776
|
attribute: null,
|
|
853
|
-
from:
|
|
777
|
+
from: element
|
|
854
778
|
});
|
|
855
779
|
}
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
if (name === 'is' && !ALLOWED_ATTR[name]) {
|
|
780
|
+
element.removeAttribute(name);
|
|
781
|
+
// We void attribute values for unremovable "is" attributes
|
|
782
|
+
if (name === 'is') {
|
|
860
783
|
if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
|
|
861
784
|
try {
|
|
862
|
-
_forceRemove(
|
|
785
|
+
_forceRemove(element);
|
|
863
786
|
} catch (_) {}
|
|
864
787
|
} else {
|
|
865
788
|
try {
|
|
866
|
-
|
|
789
|
+
element.setAttribute(name, '');
|
|
867
790
|
} catch (_) {}
|
|
868
791
|
}
|
|
869
792
|
}
|
|
870
793
|
};
|
|
871
|
-
|
|
872
794
|
/**
|
|
873
795
|
* _initDocument
|
|
874
796
|
*
|
|
875
|
-
* @param
|
|
876
|
-
* @return
|
|
797
|
+
* @param dirty - a string of dirty markup
|
|
798
|
+
* @return a DOM, filled with the dirty markup
|
|
877
799
|
*/
|
|
878
800
|
const _initDocument = function _initDocument(dirty) {
|
|
879
801
|
/* Create a HTML document */
|
|
@@ -900,7 +822,6 @@ function createDOMPurify() {
|
|
|
900
822
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
901
823
|
} catch (_) {}
|
|
902
824
|
}
|
|
903
|
-
|
|
904
825
|
/* Use createHTMLDocument in case DOMParser is not available */
|
|
905
826
|
if (!doc || !doc.documentElement) {
|
|
906
827
|
doc = implementation.createDocument(NAMESPACE, 'template', null);
|
|
@@ -914,112 +835,86 @@ function createDOMPurify() {
|
|
|
914
835
|
if (dirty && leadingWhitespace) {
|
|
915
836
|
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
|
|
916
837
|
}
|
|
917
|
-
|
|
918
838
|
/* Work on whole document or just its body */
|
|
919
839
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
920
840
|
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
|
|
921
841
|
}
|
|
922
842
|
return WHOLE_DOCUMENT ? doc.documentElement : body;
|
|
923
843
|
};
|
|
924
|
-
|
|
925
844
|
/**
|
|
926
845
|
* Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
|
|
927
846
|
*
|
|
928
|
-
* @param
|
|
929
|
-
* @return
|
|
847
|
+
* @param root The root element or node to start traversing on.
|
|
848
|
+
* @return The created NodeIterator
|
|
930
849
|
*/
|
|
931
850
|
const _createNodeIterator = function _createNodeIterator(root) {
|
|
932
851
|
return createNodeIterator.call(root.ownerDocument || root, root,
|
|
933
852
|
// eslint-disable-next-line no-bitwise
|
|
934
853
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
935
854
|
};
|
|
936
|
-
|
|
937
855
|
/**
|
|
938
856
|
* _isClobbered
|
|
939
857
|
*
|
|
940
|
-
* @param
|
|
941
|
-
* @return
|
|
858
|
+
* @param element element to check for clobbering attacks
|
|
859
|
+
* @return true if clobbered, false if safe
|
|
942
860
|
*/
|
|
943
|
-
const _isClobbered = function _isClobbered(
|
|
944
|
-
return
|
|
861
|
+
const _isClobbered = function _isClobbered(element) {
|
|
862
|
+
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');
|
|
945
863
|
};
|
|
946
|
-
|
|
947
864
|
/**
|
|
948
865
|
* Checks whether the given object is a DOM node.
|
|
949
866
|
*
|
|
950
|
-
* @param
|
|
951
|
-
* @return
|
|
867
|
+
* @param value object to check whether it's a DOM node
|
|
868
|
+
* @return true is object is a DOM node
|
|
952
869
|
*/
|
|
953
|
-
const _isNode = function _isNode(
|
|
954
|
-
return typeof Node === 'function' &&
|
|
870
|
+
const _isNode = function _isNode(value) {
|
|
871
|
+
return typeof Node === 'function' && value instanceof Node;
|
|
955
872
|
};
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
* _executeHook
|
|
959
|
-
* Execute user configurable hooks
|
|
960
|
-
*
|
|
961
|
-
* @param {String} entryPoint Name of the hook's entry point
|
|
962
|
-
* @param {Node} currentNode node to work on with the hook
|
|
963
|
-
* @param {Object} data additional hook parameters
|
|
964
|
-
*/
|
|
965
|
-
const _executeHook = function _executeHook(entryPoint, currentNode, data) {
|
|
966
|
-
if (!hooks[entryPoint]) {
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
arrayForEach(hooks[entryPoint], hook => {
|
|
873
|
+
function _executeHooks(hooks, currentNode, data) {
|
|
874
|
+
arrayForEach(hooks, hook => {
|
|
970
875
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
971
876
|
});
|
|
972
|
-
}
|
|
973
|
-
|
|
877
|
+
}
|
|
974
878
|
/**
|
|
975
879
|
* _sanitizeElements
|
|
976
880
|
*
|
|
977
881
|
* @protect nodeName
|
|
978
882
|
* @protect textContent
|
|
979
883
|
* @protect removeChild
|
|
980
|
-
*
|
|
981
|
-
* @
|
|
982
|
-
* @return {Boolean} true if node was killed, false if left alive
|
|
884
|
+
* @param currentNode to check for permission to exist
|
|
885
|
+
* @return true if node was killed, false if left alive
|
|
983
886
|
*/
|
|
984
887
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
985
888
|
let content = null;
|
|
986
|
-
|
|
987
889
|
/* Execute a hook if present */
|
|
988
|
-
|
|
989
|
-
|
|
890
|
+
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
990
891
|
/* Check if element is clobbered or can clobber */
|
|
991
892
|
if (_isClobbered(currentNode)) {
|
|
992
893
|
_forceRemove(currentNode);
|
|
993
894
|
return true;
|
|
994
895
|
}
|
|
995
|
-
|
|
996
896
|
/* Now let's check the element's type and name */
|
|
997
897
|
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
998
|
-
|
|
999
898
|
/* Execute a hook if present */
|
|
1000
|
-
|
|
899
|
+
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
1001
900
|
tagName,
|
|
1002
901
|
allowedTags: ALLOWED_TAGS
|
|
1003
902
|
});
|
|
1004
|
-
|
|
1005
903
|
/* Detect mXSS attempts abusing namespace confusion */
|
|
1006
904
|
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
|
|
1007
905
|
_forceRemove(currentNode);
|
|
1008
906
|
return true;
|
|
1009
907
|
}
|
|
1010
|
-
|
|
1011
908
|
/* Remove any occurrence of processing instructions */
|
|
1012
909
|
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
1013
910
|
_forceRemove(currentNode);
|
|
1014
911
|
return true;
|
|
1015
912
|
}
|
|
1016
|
-
|
|
1017
913
|
/* Remove any kind of possibly harmful comments */
|
|
1018
914
|
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
1019
915
|
_forceRemove(currentNode);
|
|
1020
916
|
return true;
|
|
1021
917
|
}
|
|
1022
|
-
|
|
1023
918
|
/* Remove element if anything forbids its presence */
|
|
1024
919
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
1025
920
|
/* Check if we have a custom element to handle */
|
|
@@ -1031,7 +926,6 @@ function createDOMPurify() {
|
|
|
1031
926
|
return false;
|
|
1032
927
|
}
|
|
1033
928
|
}
|
|
1034
|
-
|
|
1035
929
|
/* Keep content except for bad-listed elements */
|
|
1036
930
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
1037
931
|
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
@@ -1048,19 +942,16 @@ function createDOMPurify() {
|
|
|
1048
942
|
_forceRemove(currentNode);
|
|
1049
943
|
return true;
|
|
1050
944
|
}
|
|
1051
|
-
|
|
1052
945
|
/* Check whether element has a valid namespace */
|
|
1053
946
|
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
1054
947
|
_forceRemove(currentNode);
|
|
1055
948
|
return true;
|
|
1056
949
|
}
|
|
1057
|
-
|
|
1058
950
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
1059
951
|
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
|
|
1060
952
|
_forceRemove(currentNode);
|
|
1061
953
|
return true;
|
|
1062
954
|
}
|
|
1063
|
-
|
|
1064
955
|
/* Sanitize element content to be template-safe */
|
|
1065
956
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
1066
957
|
/* Get the element's text content */
|
|
@@ -1075,19 +966,17 @@ function createDOMPurify() {
|
|
|
1075
966
|
currentNode.textContent = content;
|
|
1076
967
|
}
|
|
1077
968
|
}
|
|
1078
|
-
|
|
1079
969
|
/* Execute a hook if present */
|
|
1080
|
-
|
|
970
|
+
_executeHooks(hooks.afterSanitizeElements, currentNode, null);
|
|
1081
971
|
return false;
|
|
1082
972
|
};
|
|
1083
|
-
|
|
1084
973
|
/**
|
|
1085
974
|
* _isValidAttribute
|
|
1086
975
|
*
|
|
1087
|
-
* @param
|
|
1088
|
-
* @param
|
|
1089
|
-
* @param
|
|
1090
|
-
* @return
|
|
976
|
+
* @param lcTag Lowercase tag name of containing element.
|
|
977
|
+
* @param lcName Lowercase attribute name.
|
|
978
|
+
* @param value Attribute value.
|
|
979
|
+
* @return Returns true if `value` is valid, otherwise false.
|
|
1091
980
|
*/
|
|
1092
981
|
// eslint-disable-next-line complexity
|
|
1093
982
|
const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
|
|
@@ -1095,7 +984,6 @@ function createDOMPurify() {
|
|
|
1095
984
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
1096
985
|
return false;
|
|
1097
986
|
}
|
|
1098
|
-
|
|
1099
987
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
1100
988
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
1101
989
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
@@ -1117,19 +1005,17 @@ function createDOMPurify() {
|
|
|
1117
1005
|
} else ;
|
|
1118
1006
|
return true;
|
|
1119
1007
|
};
|
|
1120
|
-
|
|
1121
1008
|
/**
|
|
1122
1009
|
* _isBasicCustomElement
|
|
1123
1010
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
1124
1011
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
|
|
1125
1012
|
*
|
|
1126
|
-
* @param
|
|
1127
|
-
* @returns
|
|
1013
|
+
* @param tagName name of the tag of the node to sanitize
|
|
1014
|
+
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
1128
1015
|
*/
|
|
1129
1016
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
1130
1017
|
return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
|
|
1131
1018
|
};
|
|
1132
|
-
|
|
1133
1019
|
/**
|
|
1134
1020
|
* _sanitizeAttributes
|
|
1135
1021
|
*
|
|
@@ -1138,27 +1024,26 @@ function createDOMPurify() {
|
|
|
1138
1024
|
* @protect removeAttribute
|
|
1139
1025
|
* @protect setAttribute
|
|
1140
1026
|
*
|
|
1141
|
-
* @param
|
|
1027
|
+
* @param currentNode to sanitize
|
|
1142
1028
|
*/
|
|
1143
1029
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
1144
1030
|
/* Execute a hook if present */
|
|
1145
|
-
|
|
1031
|
+
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
1146
1032
|
const {
|
|
1147
1033
|
attributes
|
|
1148
1034
|
} = currentNode;
|
|
1149
|
-
|
|
1150
1035
|
/* Check if we have attributes; if not we might have a text node */
|
|
1151
|
-
if (!attributes) {
|
|
1036
|
+
if (!attributes || _isClobbered(currentNode)) {
|
|
1152
1037
|
return;
|
|
1153
1038
|
}
|
|
1154
1039
|
const hookEvent = {
|
|
1155
1040
|
attrName: '',
|
|
1156
1041
|
attrValue: '',
|
|
1157
1042
|
keepAttr: true,
|
|
1158
|
-
allowedAttributes: ALLOWED_ATTR
|
|
1043
|
+
allowedAttributes: ALLOWED_ATTR,
|
|
1044
|
+
forceKeepAttr: undefined
|
|
1159
1045
|
};
|
|
1160
1046
|
let l = attributes.length;
|
|
1161
|
-
|
|
1162
1047
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
1163
1048
|
while (l--) {
|
|
1164
1049
|
const attr = attributes[l];
|
|
@@ -1169,64 +1054,53 @@ function createDOMPurify() {
|
|
|
1169
1054
|
} = attr;
|
|
1170
1055
|
const lcName = transformCaseFunc(name);
|
|
1171
1056
|
let value = name === 'value' ? attrValue : stringTrim(attrValue);
|
|
1172
|
-
|
|
1173
1057
|
/* Execute a hook if present */
|
|
1174
1058
|
hookEvent.attrName = lcName;
|
|
1175
1059
|
hookEvent.attrValue = value;
|
|
1176
1060
|
hookEvent.keepAttr = true;
|
|
1177
1061
|
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
|
|
1178
|
-
|
|
1062
|
+
_executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
|
|
1179
1063
|
value = hookEvent.attrValue;
|
|
1180
|
-
|
|
1064
|
+
/* Full DOM Clobbering protection via namespace isolation,
|
|
1065
|
+
* Prefix id and name attributes with `user-content-`
|
|
1066
|
+
*/
|
|
1067
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1068
|
+
// Remove the attribute with this value
|
|
1069
|
+
_removeAttribute(name, currentNode);
|
|
1070
|
+
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1071
|
+
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1072
|
+
}
|
|
1073
|
+
/* Work around a security issue with comments inside attributes */
|
|
1074
|
+
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
|
|
1075
|
+
_removeAttribute(name, currentNode);
|
|
1076
|
+
continue;
|
|
1077
|
+
}
|
|
1181
1078
|
/* Did the hooks approve of the attribute? */
|
|
1182
1079
|
if (hookEvent.forceKeepAttr) {
|
|
1183
1080
|
continue;
|
|
1184
1081
|
}
|
|
1185
|
-
|
|
1186
1082
|
/* Remove attribute */
|
|
1187
1083
|
_removeAttribute(name, currentNode);
|
|
1188
|
-
|
|
1189
1084
|
/* Did the hooks approve of the attribute? */
|
|
1190
1085
|
if (!hookEvent.keepAttr) {
|
|
1191
1086
|
continue;
|
|
1192
1087
|
}
|
|
1193
|
-
|
|
1194
1088
|
/* Work around a security issue in jQuery 3.0 */
|
|
1195
1089
|
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
|
|
1196
1090
|
_removeAttribute(name, currentNode);
|
|
1197
1091
|
continue;
|
|
1198
1092
|
}
|
|
1199
|
-
|
|
1200
1093
|
/* Sanitize attribute content to be template-safe */
|
|
1201
1094
|
if (SAFE_FOR_TEMPLATES) {
|
|
1202
1095
|
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
1203
1096
|
value = stringReplace(value, expr, ' ');
|
|
1204
1097
|
});
|
|
1205
1098
|
}
|
|
1206
|
-
|
|
1207
1099
|
/* Is `value` valid for this attribute? */
|
|
1208
1100
|
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
1209
1101
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
1210
1102
|
continue;
|
|
1211
1103
|
}
|
|
1212
|
-
|
|
1213
|
-
/* Full DOM Clobbering protection via namespace isolation,
|
|
1214
|
-
* Prefix id and name attributes with `user-content-`
|
|
1215
|
-
*/
|
|
1216
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
1217
|
-
// Remove the attribute with this value
|
|
1218
|
-
_removeAttribute(name, currentNode);
|
|
1219
|
-
|
|
1220
|
-
// Prefix the value and later re-create the attribute with the sanitized value
|
|
1221
|
-
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
/* Work around a security issue with comments inside attributes */
|
|
1225
|
-
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
|
|
1226
|
-
_removeAttribute(name, currentNode);
|
|
1227
|
-
continue;
|
|
1228
|
-
}
|
|
1229
|
-
|
|
1230
1104
|
/* Handle attributes that require Trusted Types */
|
|
1231
1105
|
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
|
|
1232
1106
|
if (namespaceURI) ; else {
|
|
@@ -1244,7 +1118,6 @@ function createDOMPurify() {
|
|
|
1244
1118
|
}
|
|
1245
1119
|
}
|
|
1246
1120
|
}
|
|
1247
|
-
|
|
1248
1121
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
1249
1122
|
try {
|
|
1250
1123
|
if (namespaceURI) {
|
|
@@ -1260,51 +1133,34 @@ function createDOMPurify() {
|
|
|
1260
1133
|
}
|
|
1261
1134
|
} catch (_) {}
|
|
1262
1135
|
}
|
|
1263
|
-
|
|
1264
1136
|
/* Execute a hook if present */
|
|
1265
|
-
|
|
1137
|
+
_executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
|
|
1266
1138
|
};
|
|
1267
|
-
|
|
1268
1139
|
/**
|
|
1269
1140
|
* _sanitizeShadowDOM
|
|
1270
1141
|
*
|
|
1271
|
-
* @param
|
|
1142
|
+
* @param fragment to iterate over recursively
|
|
1272
1143
|
*/
|
|
1273
1144
|
const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
|
|
1274
1145
|
let shadowNode = null;
|
|
1275
1146
|
const shadowIterator = _createNodeIterator(fragment);
|
|
1276
|
-
|
|
1277
1147
|
/* Execute a hook if present */
|
|
1278
|
-
|
|
1148
|
+
_executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
|
|
1279
1149
|
while (shadowNode = shadowIterator.nextNode()) {
|
|
1280
1150
|
/* Execute a hook if present */
|
|
1281
|
-
|
|
1282
|
-
|
|
1151
|
+
_executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
|
|
1283
1152
|
/* Sanitize tags and elements */
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1153
|
+
_sanitizeElements(shadowNode);
|
|
1154
|
+
/* Check attributes next */
|
|
1155
|
+
_sanitizeAttributes(shadowNode);
|
|
1288
1156
|
/* Deep shadow DOM detected */
|
|
1289
1157
|
if (shadowNode.content instanceof DocumentFragment) {
|
|
1290
1158
|
_sanitizeShadowDOM(shadowNode.content);
|
|
1291
1159
|
}
|
|
1292
|
-
|
|
1293
|
-
/* Check attributes, sanitize if necessary */
|
|
1294
|
-
_sanitizeAttributes(shadowNode);
|
|
1295
1160
|
}
|
|
1296
|
-
|
|
1297
1161
|
/* Execute a hook if present */
|
|
1298
|
-
|
|
1162
|
+
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
1299
1163
|
};
|
|
1300
|
-
|
|
1301
|
-
/**
|
|
1302
|
-
* Sanitize
|
|
1303
|
-
* Public method providing core sanitation functionality
|
|
1304
|
-
*
|
|
1305
|
-
* @param {String|Node} dirty string or DOM node
|
|
1306
|
-
* @param {Object} cfg object
|
|
1307
|
-
*/
|
|
1308
1164
|
// eslint-disable-next-line complexity
|
|
1309
1165
|
DOMPurify.sanitize = function (dirty) {
|
|
1310
1166
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -1319,7 +1175,6 @@ function createDOMPurify() {
|
|
|
1319
1175
|
if (IS_EMPTY_INPUT) {
|
|
1320
1176
|
dirty = '<!-->';
|
|
1321
1177
|
}
|
|
1322
|
-
|
|
1323
1178
|
/* Stringify, in case dirty is an object */
|
|
1324
1179
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
1325
1180
|
if (typeof dirty.toString === 'function') {
|
|
@@ -1331,20 +1186,16 @@ function createDOMPurify() {
|
|
|
1331
1186
|
throw typeErrorCreate('toString is not a function');
|
|
1332
1187
|
}
|
|
1333
1188
|
}
|
|
1334
|
-
|
|
1335
1189
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
1336
1190
|
if (!DOMPurify.isSupported) {
|
|
1337
1191
|
return dirty;
|
|
1338
1192
|
}
|
|
1339
|
-
|
|
1340
1193
|
/* Assign config vars */
|
|
1341
1194
|
if (!SET_CONFIG) {
|
|
1342
1195
|
_parseConfig(cfg);
|
|
1343
1196
|
}
|
|
1344
|
-
|
|
1345
1197
|
/* Clean up removed elements */
|
|
1346
1198
|
DOMPurify.removed = [];
|
|
1347
|
-
|
|
1348
1199
|
/* Check if dirty is correctly typed for IN_PLACE */
|
|
1349
1200
|
if (typeof dirty === 'string') {
|
|
1350
1201
|
IN_PLACE = false;
|
|
@@ -1378,45 +1229,34 @@ function createDOMPurify() {
|
|
|
1378
1229
|
dirty.indexOf('<') === -1) {
|
|
1379
1230
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
1380
1231
|
}
|
|
1381
|
-
|
|
1382
1232
|
/* Initialize the document to work on */
|
|
1383
1233
|
body = _initDocument(dirty);
|
|
1384
|
-
|
|
1385
1234
|
/* Check we have a DOM node from the data */
|
|
1386
1235
|
if (!body) {
|
|
1387
1236
|
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
|
|
1388
1237
|
}
|
|
1389
1238
|
}
|
|
1390
|
-
|
|
1391
1239
|
/* Remove first element node (ours) if FORCE_BODY is set */
|
|
1392
1240
|
if (body && FORCE_BODY) {
|
|
1393
1241
|
_forceRemove(body.firstChild);
|
|
1394
1242
|
}
|
|
1395
|
-
|
|
1396
1243
|
/* Get node iterator */
|
|
1397
1244
|
const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
|
|
1398
|
-
|
|
1399
1245
|
/* Now start iterating over the created document */
|
|
1400
1246
|
while (currentNode = nodeIterator.nextNode()) {
|
|
1401
1247
|
/* Sanitize tags and elements */
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1248
|
+
_sanitizeElements(currentNode);
|
|
1249
|
+
/* Check attributes next */
|
|
1250
|
+
_sanitizeAttributes(currentNode);
|
|
1406
1251
|
/* Shadow DOM detected, sanitize it */
|
|
1407
1252
|
if (currentNode.content instanceof DocumentFragment) {
|
|
1408
1253
|
_sanitizeShadowDOM(currentNode.content);
|
|
1409
1254
|
}
|
|
1410
|
-
|
|
1411
|
-
/* Check attributes, sanitize if necessary */
|
|
1412
|
-
_sanitizeAttributes(currentNode);
|
|
1413
1255
|
}
|
|
1414
|
-
|
|
1415
1256
|
/* If we sanitized `dirty` in-place, return it. */
|
|
1416
1257
|
if (IN_PLACE) {
|
|
1417
1258
|
return dirty;
|
|
1418
1259
|
}
|
|
1419
|
-
|
|
1420
1260
|
/* Return sanitized string or DOM */
|
|
1421
1261
|
if (RETURN_DOM) {
|
|
1422
1262
|
if (RETURN_DOM_FRAGMENT) {
|
|
@@ -1441,12 +1281,10 @@ function createDOMPurify() {
|
|
|
1441
1281
|
return returnNode;
|
|
1442
1282
|
}
|
|
1443
1283
|
let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
|
|
1444
|
-
|
|
1445
1284
|
/* Serialize doctype if allowed */
|
|
1446
1285
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
|
|
1447
1286
|
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
|
|
1448
1287
|
}
|
|
1449
|
-
|
|
1450
1288
|
/* Sanitize final string template-safe */
|
|
1451
1289
|
if (SAFE_FOR_TEMPLATES) {
|
|
1452
1290
|
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
@@ -1455,39 +1293,15 @@ function createDOMPurify() {
|
|
|
1455
1293
|
}
|
|
1456
1294
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
|
|
1457
1295
|
};
|
|
1458
|
-
|
|
1459
|
-
/**
|
|
1460
|
-
* Public method to set the configuration once
|
|
1461
|
-
* setConfig
|
|
1462
|
-
*
|
|
1463
|
-
* @param {Object} cfg configuration object
|
|
1464
|
-
*/
|
|
1465
1296
|
DOMPurify.setConfig = function () {
|
|
1466
1297
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
1467
1298
|
_parseConfig(cfg);
|
|
1468
1299
|
SET_CONFIG = true;
|
|
1469
1300
|
};
|
|
1470
|
-
|
|
1471
|
-
/**
|
|
1472
|
-
* Public method to remove the configuration
|
|
1473
|
-
* clearConfig
|
|
1474
|
-
*
|
|
1475
|
-
*/
|
|
1476
1301
|
DOMPurify.clearConfig = function () {
|
|
1477
1302
|
CONFIG = null;
|
|
1478
1303
|
SET_CONFIG = false;
|
|
1479
1304
|
};
|
|
1480
|
-
|
|
1481
|
-
/**
|
|
1482
|
-
* Public method to check if an attribute value is valid.
|
|
1483
|
-
* Uses last set config, if any. Otherwise, uses config defaults.
|
|
1484
|
-
* isValidAttribute
|
|
1485
|
-
*
|
|
1486
|
-
* @param {String} tag Tag name of containing element.
|
|
1487
|
-
* @param {String} attr Attribute name.
|
|
1488
|
-
* @param {String} value Attribute value.
|
|
1489
|
-
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
|
|
1490
|
-
*/
|
|
1491
1305
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
1492
1306
|
/* Initialize shared config vars if necessary. */
|
|
1493
1307
|
if (!CONFIG) {
|
|
@@ -1497,54 +1311,24 @@ function createDOMPurify() {
|
|
|
1497
1311
|
const lcName = transformCaseFunc(attr);
|
|
1498
1312
|
return _isValidAttribute(lcTag, lcName, value);
|
|
1499
1313
|
};
|
|
1500
|
-
|
|
1501
|
-
/**
|
|
1502
|
-
* AddHook
|
|
1503
|
-
* Public method to add DOMPurify hooks
|
|
1504
|
-
*
|
|
1505
|
-
* @param {String} entryPoint entry point for the hook to add
|
|
1506
|
-
* @param {Function} hookFunction function to execute
|
|
1507
|
-
*/
|
|
1508
1314
|
DOMPurify.addHook = function (entryPoint, hookFunction) {
|
|
1509
1315
|
if (typeof hookFunction !== 'function') {
|
|
1510
1316
|
return;
|
|
1511
1317
|
}
|
|
1512
|
-
hooks[entryPoint] = hooks[entryPoint] || [];
|
|
1513
1318
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
1514
1319
|
};
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
* (pops it from the stack of hooks if more are present)
|
|
1520
|
-
*
|
|
1521
|
-
* @param {String} entryPoint entry point for the hook to remove
|
|
1522
|
-
* @return {Function} removed(popped) hook
|
|
1523
|
-
*/
|
|
1524
|
-
DOMPurify.removeHook = function (entryPoint) {
|
|
1525
|
-
if (hooks[entryPoint]) {
|
|
1526
|
-
return arrayPop(hooks[entryPoint]);
|
|
1320
|
+
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
1321
|
+
if (hookFunction !== undefined) {
|
|
1322
|
+
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
1323
|
+
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
1527
1324
|
}
|
|
1325
|
+
return arrayPop(hooks[entryPoint]);
|
|
1528
1326
|
};
|
|
1529
|
-
|
|
1530
|
-
/**
|
|
1531
|
-
* RemoveHooks
|
|
1532
|
-
* Public method to remove all DOMPurify hooks at a given entryPoint
|
|
1533
|
-
*
|
|
1534
|
-
* @param {String} entryPoint entry point for the hooks to remove
|
|
1535
|
-
*/
|
|
1536
1327
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
1537
|
-
|
|
1538
|
-
hooks[entryPoint] = [];
|
|
1539
|
-
}
|
|
1328
|
+
hooks[entryPoint] = [];
|
|
1540
1329
|
};
|
|
1541
|
-
|
|
1542
|
-
/**
|
|
1543
|
-
* RemoveAllHooks
|
|
1544
|
-
* Public method to remove all DOMPurify hooks
|
|
1545
|
-
*/
|
|
1546
1330
|
DOMPurify.removeAllHooks = function () {
|
|
1547
|
-
hooks =
|
|
1331
|
+
hooks = _createHooksMap();
|
|
1548
1332
|
};
|
|
1549
1333
|
return DOMPurify;
|
|
1550
1334
|
}
|