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.

Files changed (260) hide show
  1. umap/__init__.py +1 -1
  2. umap/admin.py +15 -2
  3. umap/asgi.py +12 -7
  4. umap/context_processors.py +1 -0
  5. umap/locale/br/LC_MESSAGES/django.mo +0 -0
  6. umap/locale/br/LC_MESSAGES/django.po +111 -67
  7. umap/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  8. umap/locale/cs_CZ/LC_MESSAGES/django.po +110 -66
  9. umap/locale/el/LC_MESSAGES/django.mo +0 -0
  10. umap/locale/el/LC_MESSAGES/django.po +129 -85
  11. umap/locale/en/LC_MESSAGES/django.po +103 -60
  12. umap/locale/es/LC_MESSAGES/django.mo +0 -0
  13. umap/locale/es/LC_MESSAGES/django.po +114 -69
  14. umap/locale/fr/LC_MESSAGES/django.mo +0 -0
  15. umap/locale/fr/LC_MESSAGES/django.po +105 -61
  16. umap/locale/gl/LC_MESSAGES/django.mo +0 -0
  17. umap/locale/gl/LC_MESSAGES/django.po +216 -171
  18. umap/locale/it/LC_MESSAGES/django.mo +0 -0
  19. umap/locale/it/LC_MESSAGES/django.po +142 -98
  20. umap/locale/nl/LC_MESSAGES/django.mo +0 -0
  21. umap/locale/nl/LC_MESSAGES/django.po +196 -151
  22. umap/locale/pt/LC_MESSAGES/django.mo +0 -0
  23. umap/locale/pt/LC_MESSAGES/django.po +115 -71
  24. umap/locale/zh_TW/LC_MESSAGES/django.mo +0 -0
  25. umap/locale/zh_TW/LC_MESSAGES/django.po +109 -65
  26. umap/management/commands/empty_trash.py +12 -1
  27. umap/migrations/0026_datalayer_modified_at_datalayer_share_status.py +26 -0
  28. umap/models.py +43 -13
  29. umap/settings/base.py +5 -2
  30. umap/static/umap/base.css +5 -2
  31. umap/static/umap/content.css +2 -22
  32. umap/static/umap/css/bar.css +39 -10
  33. umap/static/umap/css/contextmenu.css +14 -2
  34. umap/static/umap/css/form.css +33 -39
  35. umap/static/umap/css/icon.css +47 -5
  36. umap/static/umap/css/panel.css +20 -2
  37. umap/static/umap/css/popup.css +0 -1
  38. umap/static/umap/css/tooltip.css +33 -31
  39. umap/static/umap/img/16-white.svg +5 -3
  40. umap/static/umap/img/16.svg +1 -1
  41. umap/static/umap/img/24-white.svg +17 -16
  42. umap/static/umap/img/24.svg +29 -18
  43. umap/static/umap/img/providers/bitbucket.png +0 -0
  44. umap/static/umap/img/providers/github.png +0 -0
  45. umap/static/umap/img/providers/keycloak.png +0 -0
  46. umap/static/umap/img/providers/openstreetmap-oauth2.png +0 -0
  47. umap/static/umap/img/providers/twitter-oauth2.png +0 -0
  48. umap/static/umap/img/source/16-white.svg +6 -4
  49. umap/static/umap/img/source/16.svg +1 -1
  50. umap/static/umap/img/source/24-white.svg +20 -18
  51. umap/static/umap/img/source/24.svg +30 -19
  52. umap/static/umap/js/components/alerts/alert.js +4 -1
  53. umap/static/umap/js/modules/browser.js +8 -8
  54. umap/static/umap/js/modules/caption.js +30 -7
  55. umap/static/umap/js/modules/data/features.js +101 -56
  56. umap/static/umap/js/modules/data/layer.js +108 -83
  57. umap/static/umap/js/modules/form/builder.js +242 -0
  58. umap/static/umap/js/modules/form/fields.js +1346 -0
  59. umap/static/umap/js/modules/formatter.js +9 -8
  60. umap/static/umap/js/modules/help.js +20 -24
  61. umap/static/umap/js/modules/importer.js +6 -3
  62. umap/static/umap/js/modules/permissions.js +11 -6
  63. umap/static/umap/js/modules/rendering/icon.js +5 -1
  64. umap/static/umap/js/modules/rendering/layers/classified.js +12 -8
  65. umap/static/umap/js/modules/rendering/layers/cluster.js +11 -1
  66. umap/static/umap/js/modules/rendering/map.js +1 -23
  67. umap/static/umap/js/modules/rendering/ui.js +20 -38
  68. umap/static/umap/js/modules/rules.js +3 -2
  69. umap/static/umap/js/modules/saving.js +5 -0
  70. umap/static/umap/js/modules/schema.js +8 -6
  71. umap/static/umap/js/modules/share.js +3 -3
  72. umap/static/umap/js/modules/sync/engine.js +56 -26
  73. umap/static/umap/js/modules/sync/updaters.js +15 -6
  74. umap/static/umap/js/modules/sync/websocket.js +50 -37
  75. umap/static/umap/js/modules/tableeditor.js +3 -2
  76. umap/static/umap/js/modules/ui/bar.js +101 -9
  77. umap/static/umap/js/modules/ui/base.js +7 -24
  78. umap/static/umap/js/modules/ui/contextmenu.js +9 -2
  79. umap/static/umap/js/modules/ui/panel.js +5 -1
  80. umap/static/umap/js/modules/ui/tooltip.js +19 -11
  81. umap/static/umap/js/modules/umap.js +121 -68
  82. umap/static/umap/js/modules/utils.js +196 -12
  83. umap/static/umap/js/umap.controls.js +11 -353
  84. umap/static/umap/locale/am_ET.js +17 -5
  85. umap/static/umap/locale/am_ET.json +17 -5
  86. umap/static/umap/locale/ar.js +17 -5
  87. umap/static/umap/locale/ar.json +17 -5
  88. umap/static/umap/locale/ast.js +17 -5
  89. umap/static/umap/locale/ast.json +17 -5
  90. umap/static/umap/locale/bg.js +17 -5
  91. umap/static/umap/locale/bg.json +17 -5
  92. umap/static/umap/locale/br.js +33 -20
  93. umap/static/umap/locale/br.json +33 -20
  94. umap/static/umap/locale/ca.js +17 -5
  95. umap/static/umap/locale/ca.json +17 -5
  96. umap/static/umap/locale/cs_CZ.js +15 -5
  97. umap/static/umap/locale/cs_CZ.json +15 -5
  98. umap/static/umap/locale/da.js +17 -5
  99. umap/static/umap/locale/da.json +17 -5
  100. umap/static/umap/locale/de.js +17 -5
  101. umap/static/umap/locale/de.json +17 -5
  102. umap/static/umap/locale/el.js +63 -51
  103. umap/static/umap/locale/el.json +63 -51
  104. umap/static/umap/locale/en.js +15 -5
  105. umap/static/umap/locale/en.json +15 -5
  106. umap/static/umap/locale/en_US.json +17 -5
  107. umap/static/umap/locale/es.js +25 -13
  108. umap/static/umap/locale/es.json +25 -13
  109. umap/static/umap/locale/et.js +17 -5
  110. umap/static/umap/locale/et.json +17 -5
  111. umap/static/umap/locale/eu.js +17 -5
  112. umap/static/umap/locale/eu.json +17 -5
  113. umap/static/umap/locale/fa_IR.js +17 -5
  114. umap/static/umap/locale/fa_IR.json +17 -5
  115. umap/static/umap/locale/fi.js +17 -5
  116. umap/static/umap/locale/fi.json +17 -5
  117. umap/static/umap/locale/fr.js +16 -6
  118. umap/static/umap/locale/fr.json +16 -6
  119. umap/static/umap/locale/gl.js +357 -345
  120. umap/static/umap/locale/gl.json +357 -345
  121. umap/static/umap/locale/he.js +17 -5
  122. umap/static/umap/locale/he.json +17 -5
  123. umap/static/umap/locale/hr.js +17 -5
  124. umap/static/umap/locale/hr.json +17 -5
  125. umap/static/umap/locale/hu.js +14 -5
  126. umap/static/umap/locale/hu.json +14 -5
  127. umap/static/umap/locale/id.js +17 -5
  128. umap/static/umap/locale/id.json +17 -5
  129. umap/static/umap/locale/is.js +17 -5
  130. umap/static/umap/locale/is.json +17 -5
  131. umap/static/umap/locale/it.js +125 -113
  132. umap/static/umap/locale/it.json +125 -113
  133. umap/static/umap/locale/ja.js +17 -5
  134. umap/static/umap/locale/ja.json +17 -5
  135. umap/static/umap/locale/ko.js +17 -5
  136. umap/static/umap/locale/ko.json +17 -5
  137. umap/static/umap/locale/lt.js +17 -5
  138. umap/static/umap/locale/lt.json +17 -5
  139. umap/static/umap/locale/ms.js +17 -5
  140. umap/static/umap/locale/ms.json +17 -5
  141. umap/static/umap/locale/nl.js +132 -119
  142. umap/static/umap/locale/nl.json +132 -119
  143. umap/static/umap/locale/no.js +17 -5
  144. umap/static/umap/locale/no.json +17 -5
  145. umap/static/umap/locale/pl.js +17 -5
  146. umap/static/umap/locale/pl.json +17 -5
  147. umap/static/umap/locale/pl_PL.json +17 -5
  148. umap/static/umap/locale/pt.js +38 -25
  149. umap/static/umap/locale/pt.json +38 -25
  150. umap/static/umap/locale/pt_BR.js +17 -5
  151. umap/static/umap/locale/pt_BR.json +17 -5
  152. umap/static/umap/locale/pt_PT.js +17 -5
  153. umap/static/umap/locale/pt_PT.json +17 -5
  154. umap/static/umap/locale/ro.js +17 -5
  155. umap/static/umap/locale/ro.json +17 -5
  156. umap/static/umap/locale/ru.js +17 -5
  157. umap/static/umap/locale/ru.json +17 -5
  158. umap/static/umap/locale/sk_SK.js +17 -5
  159. umap/static/umap/locale/sk_SK.json +17 -5
  160. umap/static/umap/locale/sl.js +17 -5
  161. umap/static/umap/locale/sl.json +17 -5
  162. umap/static/umap/locale/sr.js +17 -5
  163. umap/static/umap/locale/sr.json +17 -5
  164. umap/static/umap/locale/sv.js +17 -5
  165. umap/static/umap/locale/sv.json +17 -5
  166. umap/static/umap/locale/th_TH.js +17 -5
  167. umap/static/umap/locale/th_TH.json +17 -5
  168. umap/static/umap/locale/tr.js +17 -5
  169. umap/static/umap/locale/tr.json +17 -5
  170. umap/static/umap/locale/uk_UA.js +17 -5
  171. umap/static/umap/locale/uk_UA.json +17 -5
  172. umap/static/umap/locale/vi.js +17 -5
  173. umap/static/umap/locale/vi.json +17 -5
  174. umap/static/umap/locale/vi_VN.json +17 -5
  175. umap/static/umap/locale/zh.js +17 -5
  176. umap/static/umap/locale/zh.json +17 -5
  177. umap/static/umap/locale/zh_CN.json +17 -5
  178. umap/static/umap/locale/zh_TW.Big5.json +17 -5
  179. umap/static/umap/locale/zh_TW.js +15 -5
  180. umap/static/umap/locale/zh_TW.json +15 -5
  181. umap/static/umap/map.css +29 -76
  182. umap/static/umap/nav.css +6 -3
  183. umap/static/umap/unittests/utils.js +14 -0
  184. umap/static/umap/vars.css +3 -0
  185. umap/static/umap/vendors/dompurify/purify.es.js +138 -354
  186. umap/static/umap/vendors/dompurify/purify.es.mjs.map +1 -1
  187. umap/static/umap/vendors/editable/Leaflet.Editable.js +1 -0
  188. umap/sync/__init__.py +0 -0
  189. umap/sync/app.py +187 -0
  190. umap/sync/payloads.py +56 -0
  191. umap/templates/auth/user_detail.html +4 -0
  192. umap/templates/auth/user_form.html +9 -6
  193. umap/templates/auth/user_stars.html +4 -0
  194. umap/templates/base.html +1 -1
  195. umap/templates/registration/login.html +2 -5
  196. umap/templates/umap/about.html +5 -0
  197. umap/templates/umap/about_summary.html +2 -2
  198. umap/templates/umap/components/provider.html +8 -0
  199. umap/templates/umap/content_footer.html +1 -1
  200. umap/templates/umap/css.html +0 -2
  201. umap/templates/umap/js.html +0 -4
  202. umap/templates/umap/map_detail.html +1 -1
  203. umap/templates/umap/password_change.html +4 -0
  204. umap/templates/umap/password_change_done.html +4 -0
  205. umap/templates/umap/search.html +4 -0
  206. umap/templates/umap/search_bar.html +1 -0
  207. umap/templates/umap/team_confirm_delete.html +4 -0
  208. umap/templates/umap/team_detail.html +4 -0
  209. umap/templates/umap/team_form.html +4 -0
  210. umap/templates/umap/user_dashboard.html +1 -1
  211. umap/templates/umap/user_teams.html +4 -0
  212. umap/tests/base.py +3 -1
  213. umap/tests/integration/conftest.py +16 -23
  214. umap/tests/integration/test_anonymous_owned_map.py +2 -2
  215. umap/tests/integration/test_basics.py +4 -7
  216. umap/tests/integration/test_caption.py +1 -0
  217. umap/tests/integration/test_categorized_layer.py +4 -8
  218. umap/tests/integration/test_choropleth.py +1 -1
  219. umap/tests/integration/test_conditional_rules.py +3 -3
  220. umap/tests/integration/test_draw_polygon.py +14 -22
  221. umap/tests/integration/test_draw_polyline.py +6 -14
  222. umap/tests/integration/test_edit_datalayer.py +11 -11
  223. umap/tests/integration/test_edit_map.py +30 -4
  224. umap/tests/integration/test_edit_marker.py +5 -5
  225. umap/tests/integration/test_edit_polygon.py +6 -6
  226. umap/tests/integration/test_features_id_generation.py +2 -6
  227. umap/tests/integration/test_import.py +115 -29
  228. umap/tests/integration/test_optimistic_merge.py +1 -0
  229. umap/tests/integration/test_owned_map.py +1 -1
  230. umap/tests/integration/test_picto.py +8 -8
  231. umap/tests/integration/test_save.py +3 -2
  232. umap/tests/integration/test_star.py +13 -9
  233. umap/tests/integration/test_tableeditor.py +8 -7
  234. umap/tests/integration/test_view_marker.py +10 -0
  235. umap/tests/integration/test_websocket_sync.py +239 -64
  236. umap/tests/settings.py +2 -0
  237. umap/tests/test_datalayer.py +2 -3
  238. umap/tests/test_datalayer_views.py +20 -1
  239. umap/tests/test_empty_trash.py +10 -3
  240. umap/tests/test_map_views.py +11 -0
  241. umap/utils.py +27 -11
  242. umap/views.py +37 -6
  243. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/METADATA +22 -22
  244. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/RECORD +247 -248
  245. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/WHEEL +1 -1
  246. umap/management/commands/run_websocket_server.py +0 -23
  247. umap/settings/local_s3.py +0 -45
  248. umap/static/umap/bitbucket.png +0 -0
  249. umap/static/umap/github.png +0 -0
  250. umap/static/umap/js/umap.forms.js +0 -1242
  251. umap/static/umap/keycloak.png +0 -0
  252. umap/static/umap/openstreetmap.png +0 -0
  253. umap/static/umap/twitter.png +0 -0
  254. umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js +0 -468
  255. umap/static/umap/vendors/toolbar/leaflet.toolbar.css +0 -1
  256. umap/static/umap/vendors/toolbar/leaflet.toolbar.js +0 -1
  257. umap/tests/test_websocket_server.py +0 -22
  258. umap/websocket_server.py +0 -202
  259. {umap_project-2.8.2.dist-info → umap_project-2.9.0.dist-info}/entry_points.txt +0 -0
  260. {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.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */
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 {Function} func - The function to be wrapped and called.
56
- * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
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 {Function} func - The constructor function to be wrapped and called.
71
- * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
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 {Object} set - The set to which elements will be added.
86
- * @param {Array} array - The array containing elements to be added to the set.
87
- * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
88
- * @returns {Object} The modified set with added elements.
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 {Array} array - The array to be cleaned.
120
- * @returns {Array} The cleaned version of the array
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 {Object} object - The object to be cloned.
136
- * @returns {Object} A new object that copies the original.
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 {Object} object - The object to look up the getter function in its prototype chain.
159
- * @param {String} prop - The property name for which to find the getter function.
160
- * @returns {Function} The getter function found in the prototype chain or a fallback function.
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(/\${[\w\W]*}/gm);
208
- const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
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
- CUSTOM_ELEMENT: CUSTOM_ELEMENT
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 {TrustedTypePolicyFactory} trustedTypes The policy factory.
257
- * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
258
- * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
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 DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
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 {Object} cfg optional config literal
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
- // eslint-disable-line indent
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 {Element} element a DOM element whose namespace is being checked
736
- * @returns {boolean} Return false if the element has a
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 {Node} node a DOM node
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 {String} name an Attribute name
842
- * @param {Node} node a DOM node
765
+ * @param name an Attribute name
766
+ * @param element a DOM node
843
767
  */
844
- const _removeAttribute = function _removeAttribute(name, node) {
768
+ const _removeAttribute = function _removeAttribute(name, element) {
845
769
  try {
846
770
  arrayPush(DOMPurify.removed, {
847
- attribute: node.getAttributeNode(name),
848
- from: node
771
+ attribute: element.getAttributeNode(name),
772
+ from: element
849
773
  });
850
774
  } catch (_) {
851
775
  arrayPush(DOMPurify.removed, {
852
776
  attribute: null,
853
- from: node
777
+ from: element
854
778
  });
855
779
  }
856
- node.removeAttribute(name);
857
-
858
- // We void attribute values for unremovable "is"" attributes
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(node);
785
+ _forceRemove(element);
863
786
  } catch (_) {}
864
787
  } else {
865
788
  try {
866
- node.setAttribute(name, '');
789
+ element.setAttribute(name, '');
867
790
  } catch (_) {}
868
791
  }
869
792
  }
870
793
  };
871
-
872
794
  /**
873
795
  * _initDocument
874
796
  *
875
- * @param {String} dirty a string of dirty markup
876
- * @return {Document} a DOM, filled with the dirty markup
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 {Node} root The root element or node to start traversing on.
929
- * @return {NodeIterator} The created NodeIterator
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 {Node} elm element to check for clobbering attacks
941
- * @return {Boolean} true if clobbered, false if safe
858
+ * @param element element to check for clobbering attacks
859
+ * @return true if clobbered, false if safe
942
860
  */
943
- const _isClobbered = function _isClobbered(elm) {
944
- return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
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 {Node} object object to check whether it's a DOM node
951
- * @return {Boolean} true is object is a DOM node
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(object) {
954
- return typeof Node === 'function' && object instanceof Node;
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
- * @param {Node} currentNode to check for permission to exist
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
- _executeHook('beforeSanitizeElements', currentNode, null);
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
- _executeHook('uponSanitizeElement', currentNode, {
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
- _executeHook('afterSanitizeElements', currentNode, null);
970
+ _executeHooks(hooks.afterSanitizeElements, currentNode, null);
1081
971
  return false;
1082
972
  };
1083
-
1084
973
  /**
1085
974
  * _isValidAttribute
1086
975
  *
1087
- * @param {string} lcTag Lowercase tag name of containing element.
1088
- * @param {string} lcName Lowercase attribute name.
1089
- * @param {string} value Attribute value.
1090
- * @return {Boolean} Returns true if `value` is valid, otherwise false.
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 {string} tagName name of the tag of the node to sanitize
1127
- * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
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 {Node} currentNode to sanitize
1027
+ * @param currentNode to sanitize
1142
1028
  */
1143
1029
  const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1144
1030
  /* Execute a hook if present */
1145
- _executeHook('beforeSanitizeAttributes', currentNode, null);
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
- _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
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
- _executeHook('afterSanitizeAttributes', currentNode, null);
1137
+ _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
1266
1138
  };
1267
-
1268
1139
  /**
1269
1140
  * _sanitizeShadowDOM
1270
1141
  *
1271
- * @param {DocumentFragment} fragment to iterate over recursively
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
- _executeHook('beforeSanitizeShadowDOM', fragment, null);
1148
+ _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
1279
1149
  while (shadowNode = shadowIterator.nextNode()) {
1280
1150
  /* Execute a hook if present */
1281
- _executeHook('uponSanitizeShadowNode', shadowNode, null);
1282
-
1151
+ _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
1283
1152
  /* Sanitize tags and elements */
1284
- if (_sanitizeElements(shadowNode)) {
1285
- continue;
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
- _executeHook('afterSanitizeShadowDOM', fragment, null);
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
- if (_sanitizeElements(currentNode)) {
1403
- continue;
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
- * RemoveHook
1518
- * Public method to remove a DOMPurify hook at a given entryPoint
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
- if (hooks[entryPoint]) {
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
  }