@cognigy/webchat 3.45.0 → 3.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/OSS_LICENSES.txt +4 -4
- package/dist/webchat.esm.js +1303 -367
- package/dist/webchat.js +31 -29
- package/package.json +3 -3
package/dist/webchat.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* webchat.esm.js v3.
|
|
3
|
-
* https://github.com/Cognigy/Webchat/tree/v3.
|
|
4
|
-
* https://github.com/Cognigy/Webchat/tree/v3.
|
|
2
|
+
* webchat.esm.js v3.47.0
|
|
3
|
+
* https://github.com/Cognigy/Webchat/tree/v3.47.0
|
|
4
|
+
* https://github.com/Cognigy/Webchat/tree/v3.47.0/OSS_LICENSES.txt
|
|
5
5
|
*/
|
|
6
6
|
import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react";
|
|
7
7
|
import * as __WEBPACK_EXTERNAL_MODULE_react_dom_7dac9eee__ from "react-dom";
|
|
@@ -38088,6 +38088,25 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38088
38088
|
}
|
|
38089
38089
|
}
|
|
38090
38090
|
};
|
|
38091
|
+
|
|
38092
|
+
/**
|
|
38093
|
+
* The webchat window (#webchatWindow) is a fixed-size flex shell with `overflow: hidden`; its
|
|
38094
|
+
* children (header, chat log, input) manage their own scrolling. Safari/WebKit, however, still
|
|
38095
|
+
* scrolls an `overflow: hidden` ancestor to bring a newly focused element into view — so during
|
|
38096
|
+
* (reverse) keyboard navigation it nudges the whole window by a few pixels, shifting the header
|
|
38097
|
+
* up and clipping the layout. Nothing else resets it, so the broken offset sticks (AB#105695).
|
|
38098
|
+
* Snap the window back to its origin whenever it is scrolled. This is browser- and message-type
|
|
38099
|
+
* agnostic and never interferes with the inner scroll containers.
|
|
38100
|
+
*/
|
|
38101
|
+
handleWebchatWindowScroll = event => {
|
|
38102
|
+
// Only act on the window's own scroll, never on a descendant scroll container that might
|
|
38103
|
+
// surface here. React 18 does not bubble synthetic scroll events, but this keeps the
|
|
38104
|
+
// target-only intent explicit and resilient to any future change in event delegation.
|
|
38105
|
+
if (event.target !== event.currentTarget) return;
|
|
38106
|
+
const el = event.currentTarget;
|
|
38107
|
+
if (el.scrollTop !== 0) el.scrollTop = 0;
|
|
38108
|
+
if (el.scrollLeft !== 0) el.scrollLeft = 0;
|
|
38109
|
+
};
|
|
38091
38110
|
handleSendRating = ({
|
|
38092
38111
|
rating,
|
|
38093
38112
|
comment,
|
|
@@ -38191,10 +38210,20 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38191
38210
|
/**
|
|
38192
38211
|
* Emits the `webchat/close-button` analytics event.
|
|
38193
38212
|
*
|
|
38194
|
-
* Fired
|
|
38195
|
-
* (`handleCloseAndReset`)
|
|
38196
|
-
* NOT emitted for the toggle button,
|
|
38197
|
-
* or the programmatic `webchat.close()` /
|
|
38213
|
+
* Fired from every close ("X") button handler — the chat window header
|
|
38214
|
+
* (`handleCloseAndReset`), the disconnect overlay (`handleClose`) and the home
|
|
38215
|
+
* screen (`handleHomeScreenClose`). It is NOT emitted for the toggle button,
|
|
38216
|
+
* the header minimize button, or the programmatic `webchat.close()` /
|
|
38217
|
+
* `webchat.toggle()` APIs.
|
|
38218
|
+
*
|
|
38219
|
+
* The payload carries the connection status at close time so integrations can
|
|
38220
|
+
* decide whether acting on the close applies: `connected` is the live socket
|
|
38221
|
+
* state, and `hadConnection` is `true` once a session has connected this visit
|
|
38222
|
+
* (and stays `true` after it drops — e.g. the disconnect overlay, or a home
|
|
38223
|
+
* screen returned to after connecting). It is `false` only before the first
|
|
38224
|
+
* connection (a first-visit home screen, the privacy notice, or the
|
|
38225
|
+
* previous-conversations screen), so a documented handler keyed on it leaves
|
|
38226
|
+
* those closes alone.
|
|
38198
38227
|
*
|
|
38199
38228
|
* This lives in the component rather than the redux analytics middleware
|
|
38200
38229
|
* (where `webchat/open|close|minimize` are emitted) on purpose: the store only
|
|
@@ -38202,7 +38231,10 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38202
38231
|
* the close, so the originating interaction is only known here.
|
|
38203
38232
|
*/
|
|
38204
38233
|
emitCloseButtonAnalytics = () => {
|
|
38205
|
-
this.props.onEmitAnalytics("webchat/close-button"
|
|
38234
|
+
this.props.onEmitAnalytics("webchat/close-button", {
|
|
38235
|
+
connected: this.props.connected,
|
|
38236
|
+
hadConnection: this.state.hadConnection
|
|
38237
|
+
});
|
|
38206
38238
|
};
|
|
38207
38239
|
render() {
|
|
38208
38240
|
const {
|
|
@@ -38338,6 +38370,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38338
38370
|
className: "webchat",
|
|
38339
38371
|
id: "webchatWindow",
|
|
38340
38372
|
ref: this.webchatWindowRef,
|
|
38373
|
+
onScroll: this.handleWebchatWindowScroll,
|
|
38341
38374
|
chatWindowWidth: this.props.config.settings.layout.chatWindowWidth
|
|
38342
38375
|
}), !fullscreenMessage ? this.renderRegularLayout(isInforming) : this.renderFullscreenMessageLayout(), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_DisconnectOverlay__WEBPACK_IMPORTED_MODULE_20__/* ["default"] */ .A, {
|
|
38343
38376
|
isOpen: showDisconnectOverlay,
|
|
@@ -38457,6 +38490,17 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38457
38490
|
// Restore focus to chat toggle button
|
|
38458
38491
|
this.chatToggleButtonRef?.current?.focus?.();
|
|
38459
38492
|
};
|
|
38493
|
+
const handleHomeScreenClose = () => {
|
|
38494
|
+
// The home screen's close ("X") minimizes rather than closing an active
|
|
38495
|
+
// session, but it is still a header close button, so it emits
|
|
38496
|
+
// "webchat/close-button" for parity with the chat window header and the
|
|
38497
|
+
// disconnect overlay. Its payload's `hadConnection` is false on a
|
|
38498
|
+
// first-visit home screen (nothing to act on) but true if the user
|
|
38499
|
+
// already connected and returned here — so a documented handler keyed on
|
|
38500
|
+
// `hadConnection` acts only when a session was actually started.
|
|
38501
|
+
handleOnMinimize();
|
|
38502
|
+
this.emitCloseButtonAnalytics();
|
|
38503
|
+
};
|
|
38460
38504
|
const handleOnGoBack = () => {
|
|
38461
38505
|
if (!showChatOptionsScreen && !showRatingScreen) {
|
|
38462
38506
|
onSetShowPrevConversations(false);
|
|
@@ -38616,7 +38660,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38616
38660
|
onSetShowHomeScreen: onSetShowHomeScreen,
|
|
38617
38661
|
onStartConversation: this.handleStartConversation,
|
|
38618
38662
|
onSetShowPrevConversations: onSetShowPrevConversations,
|
|
38619
|
-
onClose:
|
|
38663
|
+
onClose: handleHomeScreenClose,
|
|
38620
38664
|
config: config,
|
|
38621
38665
|
onEmitAnalytics: onEmitAnalytics,
|
|
38622
38666
|
onSendActionButtonMessage: this.handleSendActionButtonMessage
|
|
@@ -44684,6 +44728,22 @@ class Webchat extends react__WEBPACK_IMPORTED_MODULE_0__.PureComponent {
|
|
|
44684
44728
|
connect = async () => {
|
|
44685
44729
|
this.store.dispatch((0,_store_connection_connection_middleware__WEBPACK_IMPORTED_MODULE_7__/* .connect */ .Ng)());
|
|
44686
44730
|
};
|
|
44731
|
+
|
|
44732
|
+
/**
|
|
44733
|
+
* Closes the socket connection and cancels any in-flight automatic
|
|
44734
|
+
* reconnection attempts.
|
|
44735
|
+
*
|
|
44736
|
+
* Unlike `close()` (which only collapses the widget, leaving the socket and
|
|
44737
|
+
* its reconnection loop running) and `endSession()` (which switches to a
|
|
44738
|
+
* fresh session and immediately reconnects), this leaves the Webchat
|
|
44739
|
+
* disconnected. A new connection is established again when the conversation
|
|
44740
|
+
* resumes (a message is sent or the chat screen is shown) or when
|
|
44741
|
+
* connectivity is restored (the tab becomes visible or the network comes back
|
|
44742
|
+
* online).
|
|
44743
|
+
*/
|
|
44744
|
+
disconnect = () => {
|
|
44745
|
+
this.store.dispatch((0,_store_connection_connection_middleware__WEBPACK_IMPORTED_MODULE_7__/* .disconnect */ .Zf)());
|
|
44746
|
+
};
|
|
44687
44747
|
sendMessage = (text, data, options) => {
|
|
44688
44748
|
this.store.dispatch((0,_store_messages_message_middleware__WEBPACK_IMPORTED_MODULE_4__/* .sendMessage */ ._z)({
|
|
44689
44749
|
text,
|
|
@@ -56905,6 +56965,23 @@ a._button_19rd2_1._disabled_19rd2_52:focus {
|
|
|
56905
56965
|
height: 100%;
|
|
56906
56966
|
border-right: 2px solid var(--cc-primary-contrast-color);
|
|
56907
56967
|
}
|
|
56968
|
+
._srOnly_14hnz_1 {
|
|
56969
|
+
position: absolute;
|
|
56970
|
+
width: 1px;
|
|
56971
|
+
height: 1px;
|
|
56972
|
+
padding: 0;
|
|
56973
|
+
margin: -1px;
|
|
56974
|
+
overflow: hidden;
|
|
56975
|
+
clip: rect(0, 0, 0, 0);
|
|
56976
|
+
white-space: nowrap;
|
|
56977
|
+
border: 0;
|
|
56978
|
+
}
|
|
56979
|
+
|
|
56980
|
+
._divider_14hnz_13 {
|
|
56981
|
+
border-bottom-width: 1px;
|
|
56982
|
+
border-bottom-style: solid;
|
|
56983
|
+
border-bottom-color: var(--cc-black-80);
|
|
56984
|
+
}
|
|
56908
56985
|
._buttons_tq8ye_1 {
|
|
56909
56986
|
align-items: flex-start;
|
|
56910
56987
|
display: flex;
|
|
@@ -57577,23 +57654,6 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57577
57654
|
._mainButtonWrapper_zhv80_144 {
|
|
57578
57655
|
padding: 16px;
|
|
57579
57656
|
}
|
|
57580
|
-
._srOnly_14hnz_1 {
|
|
57581
|
-
position: absolute;
|
|
57582
|
-
width: 1px;
|
|
57583
|
-
height: 1px;
|
|
57584
|
-
padding: 0;
|
|
57585
|
-
margin: -1px;
|
|
57586
|
-
overflow: hidden;
|
|
57587
|
-
clip: rect(0, 0, 0, 0);
|
|
57588
|
-
white-space: nowrap;
|
|
57589
|
-
border: 0;
|
|
57590
|
-
}
|
|
57591
|
-
|
|
57592
|
-
._divider_14hnz_13 {
|
|
57593
|
-
border-bottom-width: 1px;
|
|
57594
|
-
border-bottom-style: solid;
|
|
57595
|
-
border-bottom-color: var(--cc-black-80);
|
|
57596
|
-
}
|
|
57597
57657
|
._slideItem_1prat_1 {
|
|
57598
57658
|
position: relative;
|
|
57599
57659
|
width: 206px;
|
|
@@ -58913,13 +58973,17 @@ article ._header_21mid_1 ._avatar-name_21mid_27 {
|
|
|
58913
58973
|
margin: 0;
|
|
58914
58974
|
padding: 0;
|
|
58915
58975
|
}
|
|
58916
|
-
article .
|
|
58976
|
+
article ._avatar_o5ge0_1 {
|
|
58917
58977
|
border-radius: 1000px;
|
|
58918
58978
|
width: 28px;
|
|
58919
58979
|
height: 28px;
|
|
58920
|
-
background-color: var(--cc-primary-color);
|
|
58921
58980
|
box-sizing: border-box;
|
|
58922
58981
|
}
|
|
58982
|
+
|
|
58983
|
+
/* Background only for the bundled default avatars (see Avatar.tsx); custom images stay transparent (AB#90506). */
|
|
58984
|
+
article ._avatar_o5ge0_1[data-default-avatar] {
|
|
58985
|
+
background-color: var(--cc-primary-color);
|
|
58986
|
+
}
|
|
58923
58987
|
:root {
|
|
58924
58988
|
--cc-font-family: var(--webchat-font-family, Figtree);
|
|
58925
58989
|
--cc-action-button-border-radius: var(--webchat-action-button-border-radius, 19px);
|
|
@@ -85254,11 +85318,16 @@ const Text = props2 => {
|
|
|
85254
85318
|
a: ({
|
|
85255
85319
|
node: _node,
|
|
85256
85320
|
...props22
|
|
85257
|
-
}) =>
|
|
85321
|
+
}) => (
|
|
85322
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- react-markdown
|
|
85323
|
+
component override: the link text always arrives as children via the
|
|
85324
|
+
{...props} spread from the markdown AST; the rule cannot see it. */
|
|
85325
|
+
/* @__PURE__ */
|
|
85326
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85258
85327
|
target: "_blank",
|
|
85259
85328
|
rel: "noreferrer",
|
|
85260
85329
|
...props22
|
|
85261
|
-
}),
|
|
85330
|
+
})),
|
|
85262
85331
|
p: ({
|
|
85263
85332
|
node: _node,
|
|
85264
85333
|
children,
|
|
@@ -85908,6 +85977,12 @@ const classes$e = {
|
|
|
85908
85977
|
imageDisabled,
|
|
85909
85978
|
buttonImageContainer
|
|
85910
85979
|
};
|
|
85980
|
+
const srOnly = "_srOnly_14hnz_1";
|
|
85981
|
+
const divider = "_divider_14hnz_13";
|
|
85982
|
+
const mainClasses = {
|
|
85983
|
+
srOnly,
|
|
85984
|
+
divider
|
|
85985
|
+
};
|
|
85911
85986
|
const ActionButton = props2 => {
|
|
85912
85987
|
const {
|
|
85913
85988
|
button: button2,
|
|
@@ -85930,32 +86005,29 @@ const ActionButton = props2 => {
|
|
|
85930
86005
|
const customAllowedHtmlTags = config2?.settings?.widgetSettings?.customAllowedHtmlTags;
|
|
85931
86006
|
const __html = config2?.settings?.layout?.disableHtmlContentSanitization ? buttonLabel : sanitizeHTMLWithConfig(buttonLabel, customAllowedHtmlTags);
|
|
85932
86007
|
const isPhoneNumber = button2.payload && (buttonType === "phone_number" || buttonType === "user_phone_number");
|
|
85933
|
-
const buttonTitle = button2.title || "";
|
|
85934
86008
|
const isWebURL = "type" in button2 && button2.type === "web_url";
|
|
85935
86009
|
const isWebURLButtonTargetBlank = isWebURL && button2.target !== "_self";
|
|
85936
86010
|
const opensInNewTabLabel = config2?.settings?.customTranslations?.ariaLabels?.opensInNewTab || "Opens in new tab";
|
|
85937
|
-
const
|
|
85938
|
-
|
|
85939
|
-
|
|
85940
|
-
|
|
85941
|
-
|
|
85942
|
-
|
|
85943
|
-
|
|
85944
|
-
|
|
85945
|
-
|
|
85946
|
-
} else if (total <= 1 && isURLInNewTab) {
|
|
85947
|
-
return newTabURLButtonTitle;
|
|
85948
|
-
}
|
|
85949
|
-
};
|
|
85950
|
-
const PhoneNumberAnchor = props22 => button2.payload ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
86011
|
+
const positionText = total > 1 ? interpolateString(config2?.settings?.customTranslations?.ariaLabels?.actionButtonPositionText ?? "{position} of {total}", {
|
|
86012
|
+
position: position2.toString(),
|
|
86013
|
+
total: total.toString()
|
|
86014
|
+
}) + ": " : null;
|
|
86015
|
+
const PhoneNumberAnchor = props22 => (
|
|
86016
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- render-prop component:
|
|
86017
|
+
the button title always arrives as children via the {...props} spread; the rule
|
|
86018
|
+
cannot see through the indirection. */
|
|
86019
|
+
button2.payload ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85951
86020
|
...props22,
|
|
85952
86021
|
href: `tel:${button2.payload}`
|
|
85953
|
-
}) : null;
|
|
85954
|
-
const Anchor2 = props22 =>
|
|
86022
|
+
}) : null);
|
|
86023
|
+
const Anchor2 = props22 => (
|
|
86024
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- same render-prop pattern
|
|
86025
|
+
as PhoneNumberAnchor: children come in via the {...props} spread. */
|
|
86026
|
+
isWebURL ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85955
86027
|
...props22,
|
|
85956
86028
|
href: button2.url,
|
|
85957
86029
|
target: button2.target
|
|
85958
|
-
}) : null;
|
|
86030
|
+
}) : null);
|
|
85959
86031
|
const Button2 = props22 => /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
|
|
85960
86032
|
...props22,
|
|
85961
86033
|
disabled: disabled2
|
|
@@ -86009,10 +86081,12 @@ const ActionButton = props2 => {
|
|
|
86009
86081
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(Component, {
|
|
86010
86082
|
onClick: onClick2,
|
|
86011
86083
|
className: classnames$1(classes$e.button, isWebURL && classes$e.url, props2.className, disabled2 && classes$e.disabled, disabled2 && "disabled", isPhoneNumber && "phone-number-or-url-anchor", isWebURL && "phone-number-or-url-anchor"),
|
|
86012
|
-
"aria-label": getAriaLabel(),
|
|
86013
86084
|
"aria-disabled": disabled2,
|
|
86014
86085
|
tabIndex: disabled2 ? -1 : 0,
|
|
86015
|
-
children: [
|
|
86086
|
+
children: [positionText && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
86087
|
+
className: mainClasses.srOnly,
|
|
86088
|
+
children: positionText
|
|
86089
|
+
}), !!buttonImage2 && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
|
|
86016
86090
|
className: classes$e.buttonImageContainer,
|
|
86017
86091
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
86018
86092
|
src: buttonImage2,
|
|
@@ -86026,7 +86100,10 @@ const ActionButton = props2 => {
|
|
|
86026
86100
|
__html
|
|
86027
86101
|
},
|
|
86028
86102
|
className: !!buttonImage2 && classes$e.buttonLabelWithImage
|
|
86029
|
-
}), renderIcon()
|
|
86103
|
+
}), renderIcon(), isWebURL && isWebURLButtonTargetBlank && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
86104
|
+
className: mainClasses.srOnly,
|
|
86105
|
+
children: `, ${opensInNewTabLabel}`
|
|
86106
|
+
})]
|
|
86030
86107
|
});
|
|
86031
86108
|
};
|
|
86032
86109
|
const buttons$1 = "_buttons_tq8ye_1";
|
|
@@ -92711,7 +92788,12 @@ const Controls = props2 => {
|
|
|
92711
92788
|
})]
|
|
92712
92789
|
})
|
|
92713
92790
|
})]
|
|
92714
|
-
}), altText &&
|
|
92791
|
+
}), altText &&
|
|
92792
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- intentionally
|
|
92793
|
+
content-less: hidden (display:none, aria-hidden) data-URI download target,
|
|
92794
|
+
activated programmatically by the visible "Download Transcript" button. */
|
|
92795
|
+
/* @__PURE__ */
|
|
92796
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
92715
92797
|
ref: downloadTranscriptLinkRef,
|
|
92716
92798
|
href: `data:text/plain;charset=utf-8,${encodeURIComponent(altText)}`,
|
|
92717
92799
|
download: "audio-transcript.txt",
|
|
@@ -92973,12 +93055,6 @@ const classes$9 = {
|
|
|
92973
93055
|
listItemButtonWrapper,
|
|
92974
93056
|
mainButtonWrapper
|
|
92975
93057
|
};
|
|
92976
|
-
const srOnly = "_srOnly_14hnz_1";
|
|
92977
|
-
const divider = "_divider_14hnz_13";
|
|
92978
|
-
const mainClasses = {
|
|
92979
|
-
srOnly,
|
|
92980
|
-
divider
|
|
92981
|
-
};
|
|
92982
93058
|
const ListItem = props2 => {
|
|
92983
93059
|
const {
|
|
92984
93060
|
action,
|
|
@@ -93039,9 +93115,11 @@ const ListItem = props2 => {
|
|
|
93039
93115
|
backgroundImage: getBackgroundImage(image_url)
|
|
93040
93116
|
},
|
|
93041
93117
|
"data-testid": "regular-image",
|
|
93042
|
-
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93118
|
+
children: image_alt_text ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93043
93119
|
role: "img",
|
|
93044
93120
|
"aria-label": image_alt_text
|
|
93121
|
+
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93122
|
+
"aria-hidden": "true"
|
|
93045
93123
|
})
|
|
93046
93124
|
});
|
|
93047
93125
|
}, [image_alt_text, image_url]);
|
|
@@ -118652,7 +118730,12 @@ const DatePicker = () => {
|
|
|
118652
118730
|
"aria-controls": `webchat-plugin-date-picker-${message2.id}`,
|
|
118653
118731
|
"aria-haspopup": "dialog",
|
|
118654
118732
|
children: openText
|
|
118655
|
-
}), showPicker &&
|
|
118733
|
+
}), showPicker &&
|
|
118734
|
+
/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- standard
|
|
118735
|
+
ARIA modal pattern: the keydown listener on the role="dialog" container implements
|
|
118736
|
+
the dialog's focus trap and Escape handling (APG dialog-modal), not a click target. */
|
|
118737
|
+
/* @__PURE__ */
|
|
118738
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
118656
118739
|
ref: datePickerRef,
|
|
118657
118740
|
id: `webchat-plugin-date-picker-${message2.id}`,
|
|
118658
118741
|
className: classnames$1(classes$6.wrapper, "webchat-plugin-date-picker"),
|
|
@@ -134709,7 +134792,7 @@ const classes$3 = {
|
|
|
134709
134792
|
timestamp,
|
|
134710
134793
|
"avatar-name": "_avatar-name_21mid_27"
|
|
134711
134794
|
};
|
|
134712
|
-
const avatar = "
|
|
134795
|
+
const avatar = "_avatar_o5ge0_1";
|
|
134713
134796
|
const classes$2 = {
|
|
134714
134797
|
avatar
|
|
134715
134798
|
};
|
|
@@ -134738,11 +134821,13 @@ const Avatar = props2 => {
|
|
|
134738
134821
|
setAvatarUrl(botAvatar);
|
|
134739
134822
|
}
|
|
134740
134823
|
}, [message2.avatarUrl, message2?.source, overrides.agentAvatarOverrideUrlOnce, overrides.botAvatarOverrideUrlOnce]);
|
|
134824
|
+
const isDefaultAvatar = avatarUrl === botAvatar || avatarUrl === placeholderAvatar;
|
|
134741
134825
|
const classNames = classnames$1(classes$2.avatar, props2.className, "webchat-avatar", message2?.source);
|
|
134742
134826
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
134743
134827
|
alt: "",
|
|
134744
134828
|
className: classNames,
|
|
134745
134829
|
src: avatarUrl,
|
|
134830
|
+
"data-default-avatar": isDefaultAvatar ? "" : void 0,
|
|
134746
134831
|
onError: () => setAvatarUrl(placeholderAvatar),
|
|
134747
134832
|
"data-testid": `${message2?.source || "unknown source"}-avatar`
|
|
134748
134833
|
});
|
|
@@ -172236,24 +172321,65 @@ function isUrl(fileUrlOrPath) {
|
|
|
172236
172321
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
172237
172322
|
/* harmony export */ A: () => (/* binding */ purify)
|
|
172238
172323
|
/* harmony export */ });
|
|
172239
|
-
/*! @license DOMPurify 3.4.
|
|
172324
|
+
/*! @license DOMPurify 3.4.11 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.11/LICENSE */
|
|
172240
172325
|
|
|
172241
|
-
|
|
172242
|
-
|
|
172243
|
-
|
|
172244
|
-
|
|
172245
|
-
|
|
172246
|
-
|
|
172247
|
-
|
|
172248
|
-
|
|
172249
|
-
|
|
172250
|
-
|
|
172251
|
-
|
|
172252
|
-
|
|
172253
|
-
|
|
172254
|
-
|
|
172255
|
-
|
|
172256
|
-
|
|
172326
|
+
function _arrayLikeToArray(r, a) {
|
|
172327
|
+
(null == a || a > r.length) && (a = r.length);
|
|
172328
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
172329
|
+
return n;
|
|
172330
|
+
}
|
|
172331
|
+
function _arrayWithHoles(r) {
|
|
172332
|
+
if (Array.isArray(r)) return r;
|
|
172333
|
+
}
|
|
172334
|
+
function _iterableToArrayLimit(r, l) {
|
|
172335
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
172336
|
+
if (null != t) {
|
|
172337
|
+
var e,
|
|
172338
|
+
n,
|
|
172339
|
+
i,
|
|
172340
|
+
u,
|
|
172341
|
+
a = [],
|
|
172342
|
+
f = true,
|
|
172343
|
+
o = false;
|
|
172344
|
+
try {
|
|
172345
|
+
if (i = (t = t.call(r)).next, 0 === l) ; else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
|
|
172346
|
+
} catch (r) {
|
|
172347
|
+
o = true, n = r;
|
|
172348
|
+
} finally {
|
|
172349
|
+
try {
|
|
172350
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
172351
|
+
} finally {
|
|
172352
|
+
if (o) throw n;
|
|
172353
|
+
}
|
|
172354
|
+
}
|
|
172355
|
+
return a;
|
|
172356
|
+
}
|
|
172357
|
+
}
|
|
172358
|
+
function _nonIterableRest() {
|
|
172359
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
172360
|
+
}
|
|
172361
|
+
function _slicedToArray(r, e) {
|
|
172362
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
172363
|
+
}
|
|
172364
|
+
function _unsupportedIterableToArray(r, a) {
|
|
172365
|
+
if (r) {
|
|
172366
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
172367
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
172368
|
+
return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
|
|
172369
|
+
}
|
|
172370
|
+
}
|
|
172371
|
+
|
|
172372
|
+
const entries = Object.entries,
|
|
172373
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
172374
|
+
isFrozen = Object.isFrozen,
|
|
172375
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
172376
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
172377
|
+
let freeze = Object.freeze,
|
|
172378
|
+
seal = Object.seal,
|
|
172379
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
172380
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
172381
|
+
apply = _ref.apply,
|
|
172382
|
+
construct = _ref.construct;
|
|
172257
172383
|
if (!freeze) {
|
|
172258
172384
|
freeze = function freeze(x) {
|
|
172259
172385
|
return x;
|
|
@@ -172285,13 +172411,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
|
172285
172411
|
const arrayPop = unapply(Array.prototype.pop);
|
|
172286
172412
|
const arrayPush = unapply(Array.prototype.push);
|
|
172287
172413
|
const arraySplice = unapply(Array.prototype.splice);
|
|
172414
|
+
const arrayIsArray = Array.isArray;
|
|
172288
172415
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
172289
172416
|
const stringToString = unapply(String.prototype.toString);
|
|
172290
172417
|
const stringMatch = unapply(String.prototype.match);
|
|
172291
172418
|
const stringReplace = unapply(String.prototype.replace);
|
|
172292
172419
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
172293
172420
|
const stringTrim = unapply(String.prototype.trim);
|
|
172421
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
172422
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
172423
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
172424
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
172294
172425
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
172426
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
172295
172427
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
172296
172428
|
const typeErrorCreate = unconstruct(TypeError);
|
|
172297
172429
|
/**
|
|
@@ -172341,6 +172473,9 @@ function addToSet(set, array) {
|
|
|
172341
172473
|
// Prevent prototype setters from intercepting set as a this value.
|
|
172342
172474
|
setPrototypeOf(set, null);
|
|
172343
172475
|
}
|
|
172476
|
+
if (!arrayIsArray(array)) {
|
|
172477
|
+
return set;
|
|
172478
|
+
}
|
|
172344
172479
|
let l = array.length;
|
|
172345
172480
|
while (l--) {
|
|
172346
172481
|
let element = array[l];
|
|
@@ -172381,10 +172516,13 @@ function cleanArray(array) {
|
|
|
172381
172516
|
*/
|
|
172382
172517
|
function clone(object) {
|
|
172383
172518
|
const newObject = create(null);
|
|
172384
|
-
for (const
|
|
172519
|
+
for (const _ref2 of entries(object)) {
|
|
172520
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
172521
|
+
const property = _ref3[0];
|
|
172522
|
+
const value = _ref3[1];
|
|
172385
172523
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
172386
172524
|
if (isPropertyExist) {
|
|
172387
|
-
if (
|
|
172525
|
+
if (arrayIsArray(value)) {
|
|
172388
172526
|
newObject[property] = cleanArray(value);
|
|
172389
172527
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
172390
172528
|
newObject[property] = clone(value);
|
|
@@ -172395,6 +172533,58 @@ function clone(object) {
|
|
|
172395
172533
|
}
|
|
172396
172534
|
return newObject;
|
|
172397
172535
|
}
|
|
172536
|
+
/**
|
|
172537
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
172538
|
+
*
|
|
172539
|
+
* @param value - The value to stringify.
|
|
172540
|
+
* @returns A string representation of the provided value.
|
|
172541
|
+
*/
|
|
172542
|
+
function stringifyValue(value) {
|
|
172543
|
+
switch (typeof value) {
|
|
172544
|
+
case 'string':
|
|
172545
|
+
{
|
|
172546
|
+
return value;
|
|
172547
|
+
}
|
|
172548
|
+
case 'number':
|
|
172549
|
+
{
|
|
172550
|
+
return numberToString(value);
|
|
172551
|
+
}
|
|
172552
|
+
case 'boolean':
|
|
172553
|
+
{
|
|
172554
|
+
return booleanToString(value);
|
|
172555
|
+
}
|
|
172556
|
+
case 'bigint':
|
|
172557
|
+
{
|
|
172558
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
172559
|
+
}
|
|
172560
|
+
case 'symbol':
|
|
172561
|
+
{
|
|
172562
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
172563
|
+
}
|
|
172564
|
+
case 'undefined':
|
|
172565
|
+
{
|
|
172566
|
+
return objectToString(value);
|
|
172567
|
+
}
|
|
172568
|
+
case 'function':
|
|
172569
|
+
case 'object':
|
|
172570
|
+
{
|
|
172571
|
+
if (value === null) {
|
|
172572
|
+
return objectToString(value);
|
|
172573
|
+
}
|
|
172574
|
+
const valueAsRecord = value;
|
|
172575
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
172576
|
+
if (typeof valueToString === 'function') {
|
|
172577
|
+
const stringified = valueToString(valueAsRecord);
|
|
172578
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
172579
|
+
}
|
|
172580
|
+
return objectToString(value);
|
|
172581
|
+
}
|
|
172582
|
+
default:
|
|
172583
|
+
{
|
|
172584
|
+
return objectToString(value);
|
|
172585
|
+
}
|
|
172586
|
+
}
|
|
172587
|
+
}
|
|
172398
172588
|
/**
|
|
172399
172589
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
172400
172590
|
*
|
|
@@ -172420,6 +172610,14 @@ function lookupGetter(object, prop) {
|
|
|
172420
172610
|
}
|
|
172421
172611
|
return fallbackValue;
|
|
172422
172612
|
}
|
|
172613
|
+
function isRegex(value) {
|
|
172614
|
+
try {
|
|
172615
|
+
regExpTest(value, '');
|
|
172616
|
+
return true;
|
|
172617
|
+
} catch (_unused) {
|
|
172618
|
+
return false;
|
|
172619
|
+
}
|
|
172620
|
+
}
|
|
172423
172621
|
|
|
172424
172622
|
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', 'search', 'section', 'select', 'shadow', 'slot', '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']);
|
|
172425
172623
|
const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'enterkeyhint', 'exportparts', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'inputmode', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'part', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
|
|
@@ -172435,15 +172633,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
172435
172633
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
172436
172634
|
const text = freeze(['#text']);
|
|
172437
172635
|
|
|
172438
|
-
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns'
|
|
172636
|
+
const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'command', 'commandfor', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'exportparts', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inert', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'part', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'slot', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns']);
|
|
172439
172637
|
const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'mask-type', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
|
|
172440
172638
|
const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnalign', 'columnlines', 'columnspacing', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lquote', 'lspace', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
|
|
172441
172639
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
172442
172640
|
|
|
172443
|
-
|
|
172444
|
-
const
|
|
172445
|
-
const
|
|
172446
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
172641
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
172642
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
172643
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
172447
172644
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
172448
172645
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
172449
172646
|
const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
|
|
@@ -172453,30 +172650,31 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
172453
172650
|
);
|
|
172454
172651
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
172455
172652
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
172653
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
172654
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
172655
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
172656
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
172657
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
172658
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
172659
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
172456
172660
|
|
|
172457
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
172458
|
-
__proto__: null,
|
|
172459
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
172460
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
172461
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
172462
|
-
DATA_ATTR: DATA_ATTR,
|
|
172463
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
172464
|
-
ERB_EXPR: ERB_EXPR,
|
|
172465
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
172466
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
172467
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
172468
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
172469
|
-
});
|
|
172470
|
-
|
|
172471
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
172472
172661
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
172473
172662
|
const NODE_TYPE = {
|
|
172474
172663
|
element: 1,
|
|
172664
|
+
attribute: 2,
|
|
172475
172665
|
text: 3,
|
|
172666
|
+
cdataSection: 4,
|
|
172667
|
+
entityReference: 5,
|
|
172476
172668
|
// Deprecated
|
|
172477
|
-
|
|
172669
|
+
entityNode: 6,
|
|
172670
|
+
// Deprecated
|
|
172671
|
+
processingInstruction: 7,
|
|
172478
172672
|
comment: 8,
|
|
172479
|
-
document: 9
|
|
172673
|
+
document: 9,
|
|
172674
|
+
documentType: 10,
|
|
172675
|
+
documentFragment: 11,
|
|
172676
|
+
notation: 12 // Deprecated
|
|
172677
|
+
};
|
|
172480
172678
|
const getGlobal = function getGlobal() {
|
|
172481
172679
|
return typeof window === 'undefined' ? null : window;
|
|
172482
172680
|
};
|
|
@@ -172531,10 +172729,25 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
172531
172729
|
uponSanitizeShadowNode: []
|
|
172532
172730
|
};
|
|
172533
172731
|
};
|
|
172732
|
+
/**
|
|
172733
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
172734
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
172735
|
+
* options.base when given, case-normalized via options.transform),
|
|
172736
|
+
* the fallback set otherwise.
|
|
172737
|
+
*
|
|
172738
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
172739
|
+
* @param key the configuration property to read
|
|
172740
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
172741
|
+
* @param options transform and optional base set to merge into
|
|
172742
|
+
* @returns the resolved set
|
|
172743
|
+
*/
|
|
172744
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
172745
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
172746
|
+
};
|
|
172534
172747
|
function createDOMPurify() {
|
|
172535
172748
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
172536
172749
|
const DOMPurify = root => createDOMPurify(root);
|
|
172537
|
-
DOMPurify.version = '3.4.
|
|
172750
|
+
DOMPurify.version = '3.4.11';
|
|
172538
172751
|
DOMPurify.removed = [];
|
|
172539
172752
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
172540
172753
|
// Not running in a browser, provide a factory function
|
|
@@ -172542,28 +172755,29 @@ function createDOMPurify() {
|
|
|
172542
172755
|
DOMPurify.isSupported = false;
|
|
172543
172756
|
return DOMPurify;
|
|
172544
172757
|
}
|
|
172545
|
-
let
|
|
172546
|
-
document
|
|
172547
|
-
} = window;
|
|
172758
|
+
let document = window.document;
|
|
172548
172759
|
const originalDocument = document;
|
|
172549
172760
|
const currentScript = originalDocument.currentScript;
|
|
172550
|
-
|
|
172551
|
-
|
|
172552
|
-
|
|
172553
|
-
|
|
172554
|
-
|
|
172555
|
-
|
|
172556
|
-
NamedNodeMap
|
|
172557
|
-
HTMLFormElement
|
|
172558
|
-
DOMParser,
|
|
172559
|
-
trustedTypes
|
|
172560
|
-
} = window;
|
|
172761
|
+
window.DocumentFragment;
|
|
172762
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
172763
|
+
Node = window.Node,
|
|
172764
|
+
Element = window.Element,
|
|
172765
|
+
NodeFilter = window.NodeFilter,
|
|
172766
|
+
_window$NamedNodeMap = window.NamedNodeMap;
|
|
172767
|
+
_window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;
|
|
172768
|
+
window.HTMLFormElement;
|
|
172769
|
+
const DOMParser = window.DOMParser,
|
|
172770
|
+
trustedTypes = window.trustedTypes;
|
|
172561
172771
|
const ElementPrototype = Element.prototype;
|
|
172562
172772
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
172563
172773
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
172564
172774
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
172565
172775
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
172566
172776
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
172777
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
172778
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
172779
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
172780
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
172567
172781
|
// As per issue #47, the web-components registry is inherited by a
|
|
172568
172782
|
// new document created via createHTMLDocument. As per the spec
|
|
172569
172783
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -172578,33 +172792,74 @@ function createDOMPurify() {
|
|
|
172578
172792
|
}
|
|
172579
172793
|
let trustedTypesPolicy;
|
|
172580
172794
|
let emptyHTML = '';
|
|
172581
|
-
|
|
172582
|
-
|
|
172583
|
-
|
|
172584
|
-
|
|
172585
|
-
|
|
172586
|
-
|
|
172587
|
-
|
|
172588
|
-
|
|
172589
|
-
|
|
172795
|
+
// The instance's own internal Trusted Types policy. Unlike a caller-supplied
|
|
172796
|
+
// `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws
|
|
172797
|
+
// on duplicate policy names — and is the only policy allowed to persist
|
|
172798
|
+
// across configurations and survive `clearConfig()`.
|
|
172799
|
+
let defaultTrustedTypesPolicy;
|
|
172800
|
+
let defaultTrustedTypesPolicyResolved = false;
|
|
172801
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
172802
|
+
// policy (`createHTML` or `createScriptURL`). If a supplied policy callback
|
|
172803
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
172804
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
172805
|
+
// re-entry and throw a clear, actionable error instead. The guard is shared
|
|
172806
|
+
// across both callbacks, because either one re-entering `sanitize` triggers
|
|
172807
|
+
// the same unbounded recursion.
|
|
172808
|
+
let IN_TRUSTED_TYPES_POLICY = 0;
|
|
172809
|
+
const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
|
|
172810
|
+
if (IN_TRUSTED_TYPES_POLICY > 0) {
|
|
172811
|
+
throw typeErrorCreate('A configured TRUSTED_TYPES_POLICY callback (createHTML or ' + 'createScriptURL) must not call DOMPurify.sanitize, as that causes ' + 'infinite recursion. Do not pass a policy whose callbacks wrap ' + 'DOMPurify as TRUSTED_TYPES_POLICY; see the "DOMPurify and Trusted ' + 'Types" section of the README.');
|
|
172812
|
+
}
|
|
172813
|
+
};
|
|
172814
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
172815
|
+
_assertNotInTrustedTypesPolicy();
|
|
172816
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
172817
|
+
try {
|
|
172818
|
+
return trustedTypesPolicy.createHTML(html);
|
|
172819
|
+
} finally {
|
|
172820
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
172821
|
+
}
|
|
172822
|
+
};
|
|
172823
|
+
const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
|
|
172824
|
+
_assertNotInTrustedTypesPolicy();
|
|
172825
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
172826
|
+
try {
|
|
172827
|
+
return trustedTypesPolicy.createScriptURL(scriptUrl);
|
|
172828
|
+
} finally {
|
|
172829
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
172830
|
+
}
|
|
172831
|
+
};
|
|
172832
|
+
// Lazily resolve (and cache) the instance's internal default policy.
|
|
172833
|
+
// Resolution is attempted at most once: a successful `createPolicy` cannot be
|
|
172834
|
+
// repeated (Trusted Types throws on duplicate names), and a failed or
|
|
172835
|
+
// unsupported attempt must not be retried on every parse.
|
|
172836
|
+
const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
|
|
172837
|
+
if (!defaultTrustedTypesPolicyResolved) {
|
|
172838
|
+
defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
172839
|
+
defaultTrustedTypesPolicyResolved = true;
|
|
172840
|
+
}
|
|
172841
|
+
return defaultTrustedTypesPolicy;
|
|
172842
|
+
};
|
|
172843
|
+
const _document = document,
|
|
172844
|
+
implementation = _document.implementation,
|
|
172845
|
+
createNodeIterator = _document.createNodeIterator,
|
|
172846
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
172847
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
172848
|
+
const importNode = originalDocument.importNode;
|
|
172590
172849
|
let hooks = _createHooksMap();
|
|
172591
172850
|
/**
|
|
172592
172851
|
* Expose whether this browser supports running the full DOMPurify.
|
|
172593
172852
|
*/
|
|
172594
172853
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
172595
|
-
const
|
|
172596
|
-
|
|
172597
|
-
|
|
172598
|
-
|
|
172599
|
-
|
|
172600
|
-
|
|
172601
|
-
|
|
172602
|
-
|
|
172603
|
-
|
|
172604
|
-
} = EXPRESSIONS;
|
|
172605
|
-
let {
|
|
172606
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
172607
|
-
} = EXPRESSIONS;
|
|
172854
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
172855
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
172856
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
172857
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
172858
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
172859
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
172860
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
172861
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
172862
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
172608
172863
|
/**
|
|
172609
172864
|
* We consider the elements and attributes below to be safe. Ideally
|
|
172610
172865
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -172681,6 +172936,13 @@ function createDOMPurify() {
|
|
|
172681
172936
|
let WHOLE_DOCUMENT = false;
|
|
172682
172937
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
172683
172938
|
let SET_CONFIG = false;
|
|
172939
|
+
/* Pristine allowlist bindings captured at setConfig() time. On the
|
|
172940
|
+
* persistent-config path sanitize() restores the sets from these before
|
|
172941
|
+
* the per-walk hook clone-guard, so a hook's in-call widening cannot
|
|
172942
|
+
* carry across calls. Null until setConfig() is called; reset by
|
|
172943
|
+
* clearConfig(). */
|
|
172944
|
+
let SET_CONFIG_ALLOWED_TAGS = null;
|
|
172945
|
+
let SET_CONFIG_ALLOWED_ATTR = null;
|
|
172684
172946
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
172685
172947
|
* document.body. By default, browsers might move them to document.head */
|
|
172686
172948
|
let FORCE_BODY = false;
|
|
@@ -172723,7 +172985,17 @@ function createDOMPurify() {
|
|
|
172723
172985
|
let USE_PROFILES = {};
|
|
172724
172986
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
172725
172987
|
let FORBID_CONTENTS = null;
|
|
172726
|
-
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
172988
|
+
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
172989
|
+
// <selectedcontent> mirrors the selected <option>'s subtree, cloned by
|
|
172990
|
+
// the UA (customizable <select>) — including any on* handlers — and the
|
|
172991
|
+
// engine re-mirrors synchronously whenever a removal changes which
|
|
172992
|
+
// option/selectedcontent is current, even inside DOMPurify's inert
|
|
172993
|
+
// DOMParser document. Hoisting its children on removal re-inserts a fresh
|
|
172994
|
+
// mirror target ahead of the walk, which the engine refills, looping
|
|
172995
|
+
// forever (DoS) and amplifying output. Dropping its content on removal
|
|
172996
|
+
// (rather than hoisting) breaks that cascade; the content is a duplicate
|
|
172997
|
+
// of the option, which is sanitized on its own. See campaign-3 F1/F6.
|
|
172998
|
+
'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
172727
172999
|
/* Tags that are safe for data: URIs */
|
|
172728
173000
|
let DATA_URI_TAGS = null;
|
|
172729
173001
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
@@ -172739,8 +173011,10 @@ function createDOMPurify() {
|
|
|
172739
173011
|
/* Allowed XHTML+XML namespaces */
|
|
172740
173012
|
let ALLOWED_NAMESPACES = null;
|
|
172741
173013
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
172742
|
-
|
|
172743
|
-
let
|
|
173014
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
173015
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
173016
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
173017
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
172744
173018
|
// Certain elements are allowed in both SVG and HTML
|
|
172745
173019
|
// namespace. We need to specify them explicitly
|
|
172746
173020
|
// so that they don't get erroneously deleted from
|
|
@@ -172782,15 +173056,33 @@ function createDOMPurify() {
|
|
|
172782
173056
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
172783
173057
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
172784
173058
|
/* Set configuration parameters */
|
|
172785
|
-
ALLOWED_TAGS =
|
|
172786
|
-
|
|
172787
|
-
|
|
172788
|
-
|
|
172789
|
-
|
|
172790
|
-
|
|
172791
|
-
|
|
172792
|
-
|
|
172793
|
-
|
|
173059
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
173060
|
+
transform: transformCaseFunc
|
|
173061
|
+
});
|
|
173062
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
173063
|
+
transform: transformCaseFunc
|
|
173064
|
+
});
|
|
173065
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
173066
|
+
transform: stringToString
|
|
173067
|
+
});
|
|
173068
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
173069
|
+
transform: transformCaseFunc,
|
|
173070
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
173071
|
+
});
|
|
173072
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
173073
|
+
transform: transformCaseFunc,
|
|
173074
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
173075
|
+
});
|
|
173076
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
173077
|
+
transform: transformCaseFunc
|
|
173078
|
+
});
|
|
173079
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
173080
|
+
transform: transformCaseFunc
|
|
173081
|
+
});
|
|
173082
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
173083
|
+
transform: transformCaseFunc
|
|
173084
|
+
});
|
|
173085
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
172794
173086
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
172795
173087
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
172796
173088
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -172806,20 +173098,22 @@ function createDOMPurify() {
|
|
|
172806
173098
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
172807
173099
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
172808
173100
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
172809
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
172810
|
-
NAMESPACE = cfg.NAMESPACE
|
|
172811
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
172812
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
172813
|
-
|
|
172814
|
-
|
|
172815
|
-
|
|
172816
|
-
|
|
172817
|
-
|
|
172818
|
-
|
|
172819
|
-
|
|
172820
|
-
|
|
172821
|
-
|
|
172822
|
-
|
|
173101
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
173102
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
173103
|
+
MATHML_TEXT_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'MATHML_TEXT_INTEGRATION_POINTS') && cfg.MATHML_TEXT_INTEGRATION_POINTS && typeof cfg.MATHML_TEXT_INTEGRATION_POINTS === 'object' ? clone(cfg.MATHML_TEXT_INTEGRATION_POINTS) : addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); // Default built-in map
|
|
173104
|
+
HTML_INTEGRATION_POINTS = objectHasOwnProperty(cfg, 'HTML_INTEGRATION_POINTS') && cfg.HTML_INTEGRATION_POINTS && typeof cfg.HTML_INTEGRATION_POINTS === 'object' ? clone(cfg.HTML_INTEGRATION_POINTS) : addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); // Default built-in map
|
|
173105
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
173106
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
173107
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
173108
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
173109
|
+
}
|
|
173110
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
173111
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
173112
|
+
}
|
|
173113
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
173114
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
173115
|
+
}
|
|
173116
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
172823
173117
|
if (SAFE_FOR_TEMPLATES) {
|
|
172824
173118
|
ALLOW_DATA_ATTR = false;
|
|
172825
173119
|
}
|
|
@@ -172855,36 +173149,36 @@ function createDOMPurify() {
|
|
|
172855
173149
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
172856
173150
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
172857
173151
|
/* Merge configuration parameters */
|
|
172858
|
-
if (cfg
|
|
173152
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
172859
173153
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
172860
173154
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
172861
|
-
} else {
|
|
173155
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
172862
173156
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
172863
173157
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
172864
173158
|
}
|
|
172865
173159
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
172866
173160
|
}
|
|
172867
173161
|
}
|
|
172868
|
-
if (cfg
|
|
173162
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
172869
173163
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
172870
173164
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
172871
|
-
} else {
|
|
173165
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
172872
173166
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
172873
173167
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
172874
173168
|
}
|
|
172875
173169
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
172876
173170
|
}
|
|
172877
173171
|
}
|
|
172878
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
173172
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
172879
173173
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
172880
173174
|
}
|
|
172881
|
-
if (cfg.FORBID_CONTENTS) {
|
|
173175
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
172882
173176
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172883
173177
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172884
173178
|
}
|
|
172885
173179
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
172886
173180
|
}
|
|
172887
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
173181
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
172888
173182
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172889
173183
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172890
173184
|
}
|
|
@@ -172903,6 +173197,13 @@ function createDOMPurify() {
|
|
|
172903
173197
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
172904
173198
|
delete FORBID_TAGS.tbody;
|
|
172905
173199
|
}
|
|
173200
|
+
// Re-derive the active Trusted Types policy from this configuration on
|
|
173201
|
+
// every parse. The active policy must never be sticky closure state that
|
|
173202
|
+
// outlives the config that set it: a caller-supplied policy left in place
|
|
173203
|
+
// after `clearConfig()` — or after a later call that supplied none, or
|
|
173204
|
+
// `TRUSTED_TYPES_POLICY: null` — could sign a subsequent "default"
|
|
173205
|
+
// `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.
|
|
173206
|
+
// See GHSA-vxr8-fq34-vvx9.
|
|
172906
173207
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
172907
173208
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
172908
173209
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
@@ -172910,18 +173211,45 @@ function createDOMPurify() {
|
|
|
172910
173211
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
172911
173212
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
172912
173213
|
}
|
|
172913
|
-
//
|
|
173214
|
+
// A caller-supplied policy applies to this configuration only.
|
|
173215
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
172914
173216
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
172915
|
-
// Sign local variables required by `sanitize`.
|
|
172916
|
-
|
|
173217
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
173218
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
173219
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
173220
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
173221
|
+
try {
|
|
173222
|
+
emptyHTML = _createTrustedHTML('');
|
|
173223
|
+
} catch (error) {
|
|
173224
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
173225
|
+
throw error;
|
|
173226
|
+
}
|
|
173227
|
+
} else if (cfg.TRUSTED_TYPES_POLICY === null) {
|
|
173228
|
+
// Explicit opt-out for this call: perform no Trusted Types signing and
|
|
173229
|
+
// create nothing (so a strict `trusted-types` CSP that disallows a
|
|
173230
|
+
// `dompurify` policy can still call `sanitize` from inside its own
|
|
173231
|
+
// policy — see #1422). Resetting to `undefined` rather than a sticky
|
|
173232
|
+
// `null` also drops any previously retained caller policy, so it cannot
|
|
173233
|
+
// resurface on a later call, while still allowing the next config-less
|
|
173234
|
+
// call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.
|
|
173235
|
+
trustedTypesPolicy = undefined;
|
|
173236
|
+
emptyHTML = '';
|
|
172917
173237
|
} else {
|
|
172918
|
-
//
|
|
173238
|
+
// No policy supplied: keep the currently active policy if one is set — a
|
|
173239
|
+
// previously supplied policy is intentionally sticky across config-less
|
|
173240
|
+
// calls — otherwise fall back to the instance's own internal policy,
|
|
173241
|
+
// created at most once. (A policy supplied for a *single* call still
|
|
173242
|
+
// lingers by design; what must not linger is a policy whose configuration
|
|
173243
|
+
// has been torn down via `clearConfig()`, which restores the default.)
|
|
172919
173244
|
if (trustedTypesPolicy === undefined) {
|
|
172920
|
-
trustedTypesPolicy =
|
|
173245
|
+
trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
172921
173246
|
}
|
|
172922
|
-
//
|
|
172923
|
-
|
|
172924
|
-
|
|
173247
|
+
// Sign internal variables only when a policy is active. A falsy policy
|
|
173248
|
+
// (Trusted Types unsupported, creation failed, or an explicit opt-out)
|
|
173249
|
+
// leaves `emptyHTML` as a plain string, so we never call `.createHTML` on
|
|
173250
|
+
// a non-policy and throw. See #1422.
|
|
173251
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
173252
|
+
emptyHTML = _createTrustedHTML('');
|
|
172925
173253
|
}
|
|
172926
173254
|
}
|
|
172927
173255
|
// Prevent further manipulation of configuration.
|
|
@@ -172936,6 +173264,77 @@ function createDOMPurify() {
|
|
|
172936
173264
|
* correctly. */
|
|
172937
173265
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
172938
173266
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
173267
|
+
/**
|
|
173268
|
+
* Namespace rules for an element in the SVG namespace.
|
|
173269
|
+
*
|
|
173270
|
+
* @param tagName the element's lowercase tag name
|
|
173271
|
+
* @param parent the (possibly simulated) parent node
|
|
173272
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173273
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173274
|
+
*/
|
|
173275
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
173276
|
+
// The only way to switch from HTML namespace to SVG
|
|
173277
|
+
// is via <svg>. If it happens via any other tag, then
|
|
173278
|
+
// it should be killed.
|
|
173279
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
173280
|
+
return tagName === 'svg';
|
|
173281
|
+
}
|
|
173282
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
173283
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
173284
|
+
// text integration point.
|
|
173285
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
173286
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
173287
|
+
}
|
|
173288
|
+
// We only allow elements that are defined in SVG
|
|
173289
|
+
// spec. All others are disallowed in SVG namespace.
|
|
173290
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
173291
|
+
};
|
|
173292
|
+
/**
|
|
173293
|
+
* Namespace rules for an element in the MathML namespace.
|
|
173294
|
+
*
|
|
173295
|
+
* @param tagName the element's lowercase tag name
|
|
173296
|
+
* @param parent the (possibly simulated) parent node
|
|
173297
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173298
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173299
|
+
*/
|
|
173300
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
173301
|
+
// The only way to switch from HTML namespace to MathML
|
|
173302
|
+
// is via <math>. If it happens via any other tag, then
|
|
173303
|
+
// it should be killed.
|
|
173304
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
173305
|
+
return tagName === 'math';
|
|
173306
|
+
}
|
|
173307
|
+
// The only way to switch from SVG to MathML is via
|
|
173308
|
+
// <math> and HTML integration points
|
|
173309
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
173310
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
173311
|
+
}
|
|
173312
|
+
// We only allow elements that are defined in MathML
|
|
173313
|
+
// spec. All others are disallowed in MathML namespace.
|
|
173314
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
173315
|
+
};
|
|
173316
|
+
/**
|
|
173317
|
+
* Namespace rules for an element in the HTML namespace.
|
|
173318
|
+
*
|
|
173319
|
+
* @param tagName the element's lowercase tag name
|
|
173320
|
+
* @param parent the (possibly simulated) parent node
|
|
173321
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173322
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173323
|
+
*/
|
|
173324
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
173325
|
+
// The only way to switch from SVG to HTML is via
|
|
173326
|
+
// HTML integration points, and from MathML to HTML
|
|
173327
|
+
// is via MathML text integration points
|
|
173328
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
173329
|
+
return false;
|
|
173330
|
+
}
|
|
173331
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
173332
|
+
return false;
|
|
173333
|
+
}
|
|
173334
|
+
// We disallow tags that are specific for MathML
|
|
173335
|
+
// or SVG and should never appear in HTML namespace
|
|
173336
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
173337
|
+
};
|
|
172939
173338
|
/**
|
|
172940
173339
|
* @param element a DOM element whose namespace is being checked
|
|
172941
173340
|
* @returns Return false if the element has a
|
|
@@ -172958,51 +173357,13 @@ function createDOMPurify() {
|
|
|
172958
173357
|
return false;
|
|
172959
173358
|
}
|
|
172960
173359
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
172961
|
-
|
|
172962
|
-
// is via <svg>. If it happens via any other tag, then
|
|
172963
|
-
// it should be killed.
|
|
172964
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
172965
|
-
return tagName === 'svg';
|
|
172966
|
-
}
|
|
172967
|
-
// The only way to switch from MathML to SVG is via`
|
|
172968
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
172969
|
-
// text integration points.
|
|
172970
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
172971
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
172972
|
-
}
|
|
172973
|
-
// We only allow elements that are defined in SVG
|
|
172974
|
-
// spec. All others are disallowed in SVG namespace.
|
|
172975
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
173360
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
172976
173361
|
}
|
|
172977
173362
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
172978
|
-
|
|
172979
|
-
// is via <math>. If it happens via any other tag, then
|
|
172980
|
-
// it should be killed.
|
|
172981
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
172982
|
-
return tagName === 'math';
|
|
172983
|
-
}
|
|
172984
|
-
// The only way to switch from SVG to MathML is via
|
|
172985
|
-
// <math> and HTML integration points
|
|
172986
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
172987
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
172988
|
-
}
|
|
172989
|
-
// We only allow elements that are defined in MathML
|
|
172990
|
-
// spec. All others are disallowed in MathML namespace.
|
|
172991
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
173363
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
172992
173364
|
}
|
|
172993
173365
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
172994
|
-
|
|
172995
|
-
// HTML integration points, and from MathML to HTML
|
|
172996
|
-
// is via MathML text integration points
|
|
172997
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
172998
|
-
return false;
|
|
172999
|
-
}
|
|
173000
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
173001
|
-
return false;
|
|
173002
|
-
}
|
|
173003
|
-
// We disallow tags that are specific for MathML
|
|
173004
|
-
// or SVG and should never appear in HTML namespace
|
|
173005
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
173366
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
173006
173367
|
}
|
|
173007
173368
|
// For XHTML and XML documents that support custom namespaces
|
|
173008
173369
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -173027,7 +173388,74 @@ function createDOMPurify() {
|
|
|
173027
173388
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
173028
173389
|
getParentNode(node).removeChild(node);
|
|
173029
173390
|
} catch (_) {
|
|
173391
|
+
/* The normal detach failed — this is reached for a parentless node
|
|
173392
|
+
(getParentNode() is null, so .removeChild throws). Element.prototype
|
|
173393
|
+
.remove() is itself a spec no-op on a parentless node, so a recorded
|
|
173394
|
+
"removal" would otherwise hand the caller back an intact,
|
|
173395
|
+
payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or
|
|
173396
|
+
the style-with-element-child rule decided to kill). Fail closed by
|
|
173397
|
+
throwing — exactly as a clobbered root does at the IN_PLACE entry —
|
|
173398
|
+
rather than trying to "neutralize" the node via its own methods.
|
|
173399
|
+
Neutralizing would mean calling getAttributeNames()/removeAttribute()
|
|
173400
|
+
on the node, both of which a <form> root can clobber via a named child
|
|
173401
|
+
(and _isClobbered does not even probe getAttributeNames), so the
|
|
173402
|
+
neutralize step could itself be silently defeated, leaving the payload
|
|
173403
|
+
intact. A throw touches only the cached, clobber-safe remove() and
|
|
173404
|
+
getParentNode(). Generalizes GHSA-r47g-fvhr-h676 (clobbered-form root)
|
|
173405
|
+
to every root-kill reason. REPORT-3.
|
|
173406
|
+
This lives inside the catch, so it never fires for a normally-removed
|
|
173407
|
+
in-tree node: those have a parent, removeChild() succeeds, and the
|
|
173408
|
+
catch is not entered. Only a kept (parentless) root reaches here. */
|
|
173030
173409
|
remove(node);
|
|
173410
|
+
if (!getParentNode(node)) {
|
|
173411
|
+
throw typeErrorCreate('a node selected for removal could not be detached from its tree ' + 'and cannot be safely returned; refusing to sanitize in place');
|
|
173412
|
+
}
|
|
173413
|
+
}
|
|
173414
|
+
};
|
|
173415
|
+
/**
|
|
173416
|
+
* _neutralizeRoot
|
|
173417
|
+
*
|
|
173418
|
+
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
173419
|
+
* (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
|
|
173420
|
+
* custom element's reaction detaches a node so `_forceRemove`'s deliberate
|
|
173421
|
+
* parentless guard throws, or any other re-entrant engine mutation — would
|
|
173422
|
+
* otherwise leave the caller's *live* tree half-sanitized, with everything
|
|
173423
|
+
* after the abort point still carrying its handlers. There is no safe way
|
|
173424
|
+
* to resume the walk (the tree mutated under us), so we strip the root bare:
|
|
173425
|
+
* remove every child and every attribute, then let the caller's catch see
|
|
173426
|
+
* the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
|
|
173427
|
+
* getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
|
|
173428
|
+
*
|
|
173429
|
+
* @param root the in-place root to empty
|
|
173430
|
+
*/
|
|
173431
|
+
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
173432
|
+
const childNodes = getChildNodes(root);
|
|
173433
|
+
if (childNodes) {
|
|
173434
|
+
const snapshot = [];
|
|
173435
|
+
arrayForEach(childNodes, child => {
|
|
173436
|
+
arrayPush(snapshot, child);
|
|
173437
|
+
});
|
|
173438
|
+
arrayForEach(snapshot, child => {
|
|
173439
|
+
try {
|
|
173440
|
+
remove(child);
|
|
173441
|
+
} catch (_) {
|
|
173442
|
+
/* Best-effort teardown; a still-attached child is handled below */
|
|
173443
|
+
}
|
|
173444
|
+
});
|
|
173445
|
+
}
|
|
173446
|
+
const attributes = getAttributes(root);
|
|
173447
|
+
if (attributes) {
|
|
173448
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
173449
|
+
const attribute = attributes[i];
|
|
173450
|
+
const name = attribute && attribute.name;
|
|
173451
|
+
if (typeof name === 'string') {
|
|
173452
|
+
try {
|
|
173453
|
+
root.removeAttribute(name);
|
|
173454
|
+
} catch (_) {
|
|
173455
|
+
/* Clobbered removeAttribute — ignore (fail-closed best effort) */
|
|
173456
|
+
}
|
|
173457
|
+
}
|
|
173458
|
+
}
|
|
173031
173459
|
}
|
|
173032
173460
|
};
|
|
173033
173461
|
/**
|
|
@@ -173062,6 +173490,72 @@ function createDOMPurify() {
|
|
|
173062
173490
|
}
|
|
173063
173491
|
}
|
|
173064
173492
|
};
|
|
173493
|
+
/**
|
|
173494
|
+
* _stripDisallowedAttributes
|
|
173495
|
+
*
|
|
173496
|
+
* Removes every attribute the active configuration does not allow from a
|
|
173497
|
+
* single element, using the same allowlist as the main attribute pass (so
|
|
173498
|
+
* `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
|
|
173499
|
+
* neutralise nodes that are being discarded from an in-place tree.
|
|
173500
|
+
*
|
|
173501
|
+
* @param element the element to strip
|
|
173502
|
+
*/
|
|
173503
|
+
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
173504
|
+
const attributes = getAttributes(element);
|
|
173505
|
+
if (!attributes) {
|
|
173506
|
+
return;
|
|
173507
|
+
}
|
|
173508
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
173509
|
+
const attribute = attributes[i];
|
|
173510
|
+
const name = attribute && attribute.name;
|
|
173511
|
+
if (typeof name !== 'string' || ALLOWED_ATTR[transformCaseFunc(name)]) {
|
|
173512
|
+
continue;
|
|
173513
|
+
}
|
|
173514
|
+
try {
|
|
173515
|
+
element.removeAttribute(name);
|
|
173516
|
+
} catch (_) {
|
|
173517
|
+
/* Clobbered removeAttribute on a doomed node — ignore */
|
|
173518
|
+
}
|
|
173519
|
+
}
|
|
173520
|
+
};
|
|
173521
|
+
/**
|
|
173522
|
+
* _neutralizeSubtree
|
|
173523
|
+
*
|
|
173524
|
+
* Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
|
|
173525
|
+
* move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
|
|
173526
|
+
* namespace, comment, processing-instruction and KEEP_CONTENT:false removals
|
|
173527
|
+
* all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
|
|
173528
|
+
* those dropped nodes are detached from the caller's LIVE tree but a
|
|
173529
|
+
* handler-bearing original among them (an `<img onerror>`/`<video>` that was
|
|
173530
|
+
* loading) keeps its queued resource event, which fires in page scope after
|
|
173531
|
+
* sanitize returns. This walks a removed subtree and strips every attribute
|
|
173532
|
+
* the active configuration does not allow — so `on*` handlers are cancelled
|
|
173533
|
+
* through the SAME allowlist that governs kept nodes, not a separate `/^on/`
|
|
173534
|
+
* blocklist. Run synchronously before sanitize returns, i.e. before any
|
|
173535
|
+
* queued event can fire. Hook-free by design: these nodes leave the output,
|
|
173536
|
+
* so firing attribute hooks for them would be surprising. Clobber-safe reads;
|
|
173537
|
+
* a doomed clobbered node may shadow `removeAttribute` (its own attributes are
|
|
173538
|
+
* irrelevant — it is discarded — while its non-clobbered descendants, e.g.
|
|
173539
|
+
* the `<img>`, are reached and scrubbed).
|
|
173540
|
+
*
|
|
173541
|
+
* @param root the root of a removed subtree to neutralise
|
|
173542
|
+
*/
|
|
173543
|
+
const _neutralizeSubtree = function _neutralizeSubtree(root) {
|
|
173544
|
+
const stack = [root];
|
|
173545
|
+
while (stack.length > 0) {
|
|
173546
|
+
const node = stack.pop();
|
|
173547
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
173548
|
+
if (nodeType === NODE_TYPE.element) {
|
|
173549
|
+
_stripDisallowedAttributes(node);
|
|
173550
|
+
}
|
|
173551
|
+
const childNodes = getChildNodes(node);
|
|
173552
|
+
if (childNodes) {
|
|
173553
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
173554
|
+
stack.push(childNodes[i]);
|
|
173555
|
+
}
|
|
173556
|
+
}
|
|
173557
|
+
}
|
|
173558
|
+
};
|
|
173065
173559
|
/**
|
|
173066
173560
|
* _initDocument
|
|
173067
173561
|
*
|
|
@@ -173083,7 +173577,7 @@ function createDOMPurify() {
|
|
|
173083
173577
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
173084
173578
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
173085
173579
|
}
|
|
173086
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
173580
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
173087
173581
|
/*
|
|
173088
173582
|
* Use the DOMParser API by default, fallback later if needs be
|
|
173089
173583
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -173123,29 +173617,254 @@ function createDOMPurify() {
|
|
|
173123
173617
|
// eslint-disable-next-line no-bitwise
|
|
173124
173618
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
173125
173619
|
};
|
|
173620
|
+
/**
|
|
173621
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
173622
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
173623
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
173624
|
+
*
|
|
173625
|
+
* @param value the string to scrub
|
|
173626
|
+
* @returns the scrubbed string
|
|
173627
|
+
*/
|
|
173628
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
173629
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
173630
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
173631
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
173632
|
+
return value;
|
|
173633
|
+
};
|
|
173634
|
+
/**
|
|
173635
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
173636
|
+
* character data of an element subtree. Used as the final safety net for
|
|
173637
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
173638
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
173639
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
173640
|
+
*
|
|
173641
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
173642
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
173643
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
173644
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
173645
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
173646
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
173647
|
+
*
|
|
173648
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
173649
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
173650
|
+
*
|
|
173651
|
+
* @param node The root element whose character data should be scrubbed.
|
|
173652
|
+
*/
|
|
173653
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
173654
|
+
var _node$querySelectorAl;
|
|
173655
|
+
node.normalize();
|
|
173656
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
173657
|
+
// eslint-disable-next-line no-bitwise
|
|
173658
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
173659
|
+
let currentNode = walker.nextNode();
|
|
173660
|
+
while (currentNode) {
|
|
173661
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
173662
|
+
currentNode = walker.nextNode();
|
|
173663
|
+
}
|
|
173664
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
173665
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
173666
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
173667
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
173668
|
+
if (templates) {
|
|
173669
|
+
arrayForEach(templates, tmpl => {
|
|
173670
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
173671
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
173672
|
+
}
|
|
173673
|
+
});
|
|
173674
|
+
}
|
|
173675
|
+
};
|
|
173126
173676
|
/**
|
|
173127
173677
|
* _isClobbered
|
|
173128
173678
|
*
|
|
173679
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
173680
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
173681
|
+
* `name` attribute matching a prototype property shadows that property
|
|
173682
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
173683
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
173684
|
+
*
|
|
173129
173685
|
* @param element element to check for clobbering attacks
|
|
173130
173686
|
* @return true if clobbered, false if safe
|
|
173131
173687
|
*/
|
|
173132
173688
|
const _isClobbered = function _isClobbered(element) {
|
|
173133
|
-
|
|
173689
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
173690
|
+
// name at all, we can't reason about clobbering — return false
|
|
173691
|
+
// (the caller's other defences still apply).
|
|
173692
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
173693
|
+
if (typeof realTagName !== 'string') {
|
|
173694
|
+
return false;
|
|
173695
|
+
}
|
|
173696
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
173697
|
+
return false;
|
|
173698
|
+
}
|
|
173699
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
173700
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
173701
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
173702
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
173703
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
173704
|
+
// canonical NamedNodeMap.
|
|
173705
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
173706
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
173707
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
173708
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
173709
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
173710
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
173711
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
173712
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
173713
|
+
element.nodeType !== getNodeType(element) ||
|
|
173714
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
173715
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
173716
|
+
// form.childNodes from a clobbered form return the named child
|
|
173717
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
173718
|
+
// skips the form's real children. Compare the direct read to the
|
|
173719
|
+
// cached Node.prototype getter — when the form's named-property
|
|
173720
|
+
// getter intercepts the read, the two values differ and we flag
|
|
173721
|
+
// the form. This catches every clobbering child type (input,
|
|
173722
|
+
// select, etc.) regardless of whether the named child happens to
|
|
173723
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
173724
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
173725
|
+
element.childNodes !== getChildNodes(element);
|
|
173726
|
+
};
|
|
173727
|
+
/**
|
|
173728
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
173729
|
+
*
|
|
173730
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
173731
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
173732
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
173733
|
+
* internal slot, identical across realms for the same kind of node.
|
|
173734
|
+
*
|
|
173735
|
+
* @param value object to check
|
|
173736
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
173737
|
+
*/
|
|
173738
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
173739
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
173740
|
+
return false;
|
|
173741
|
+
}
|
|
173742
|
+
try {
|
|
173743
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
173744
|
+
} catch (_) {
|
|
173745
|
+
return false;
|
|
173746
|
+
}
|
|
173134
173747
|
};
|
|
173135
173748
|
/**
|
|
173136
|
-
* Checks whether the given object is a DOM node
|
|
173749
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
173750
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
173751
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
173752
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
173753
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
173754
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
173137
173755
|
*
|
|
173138
173756
|
* @param value object to check whether it's a DOM node
|
|
173139
|
-
* @return true
|
|
173757
|
+
* @return true if value is a DOM node from any realm
|
|
173140
173758
|
*/
|
|
173141
173759
|
const _isNode = function _isNode(value) {
|
|
173142
|
-
|
|
173760
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
173761
|
+
return false;
|
|
173762
|
+
}
|
|
173763
|
+
try {
|
|
173764
|
+
return typeof getNodeType(value) === 'number';
|
|
173765
|
+
} catch (_) {
|
|
173766
|
+
return false;
|
|
173767
|
+
}
|
|
173143
173768
|
};
|
|
173144
173769
|
function _executeHooks(hooks, currentNode, data) {
|
|
173770
|
+
if (hooks.length === 0) {
|
|
173771
|
+
return;
|
|
173772
|
+
}
|
|
173145
173773
|
arrayForEach(hooks, hook => {
|
|
173146
173774
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
173147
173775
|
});
|
|
173148
173776
|
}
|
|
173777
|
+
/**
|
|
173778
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
173779
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
173780
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
173781
|
+
* the caller removes. Check order is load-bearing.
|
|
173782
|
+
*
|
|
173783
|
+
* @param currentNode the node to inspect
|
|
173784
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
173785
|
+
* @return true if the node must be removed
|
|
173786
|
+
*/
|
|
173787
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
173788
|
+
/* Detect mXSS attempts abusing namespace confusion */
|
|
173789
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
173790
|
+
return true;
|
|
173791
|
+
}
|
|
173792
|
+
/* Remove risky CSS construction leading to mXSS */
|
|
173793
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
173794
|
+
return true;
|
|
173795
|
+
}
|
|
173796
|
+
/* Remove any occurrence of processing instructions */
|
|
173797
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
173798
|
+
return true;
|
|
173799
|
+
}
|
|
173800
|
+
/* Remove any kind of possibly harmful comments */
|
|
173801
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
173802
|
+
return true;
|
|
173803
|
+
}
|
|
173804
|
+
return false;
|
|
173805
|
+
};
|
|
173806
|
+
/**
|
|
173807
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
173808
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
173809
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
173810
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
173811
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
173812
|
+
*
|
|
173813
|
+
* @param currentNode the disallowed node
|
|
173814
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
173815
|
+
* @return true if the node was removed, false if kept
|
|
173816
|
+
*/
|
|
173817
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
173818
|
+
/* Check if we have a custom element to handle */
|
|
173819
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
173820
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
173821
|
+
return false;
|
|
173822
|
+
}
|
|
173823
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
173824
|
+
return false;
|
|
173825
|
+
}
|
|
173826
|
+
}
|
|
173827
|
+
/* Keep content except for bad-listed elements.
|
|
173828
|
+
Use the cached prototype getters exclusively — the previous code
|
|
173829
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
173830
|
+
fallbacks, but the cached getters always return the canonical
|
|
173831
|
+
value (or null for a real parent-less node), so the fallback
|
|
173832
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
173833
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
173834
|
+
parentNode)` check already gates correctly. */
|
|
173835
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
173836
|
+
const parentNode = getParentNode(currentNode);
|
|
173837
|
+
const childNodes = getChildNodes(currentNode);
|
|
173838
|
+
if (childNodes && parentNode) {
|
|
173839
|
+
const childCount = childNodes.length;
|
|
173840
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
173841
|
+
and sanitises them through the same allowlist pass as every other
|
|
173842
|
+
node. The caller built the tree in the live document, so the
|
|
173843
|
+
originals carry already-queued resource events (`<img onerror>`,
|
|
173844
|
+
`<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
|
|
173845
|
+
those originals detached but still armed, firing in page scope
|
|
173846
|
+
while the returned tree looked clean. Moving is safe in-place: the
|
|
173847
|
+
root is pre-validated as an allowed tag and so is never the node
|
|
173848
|
+
being removed, which keeps `parentNode` inside the iterator root
|
|
173849
|
+
and the relocated child inside the serialised tree.
|
|
173850
|
+
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
|
|
173851
|
+
at — and the result serialised from — `body`, so a restrictive
|
|
173852
|
+
ALLOWED_TAGS that removes `body` itself must leave its content in
|
|
173853
|
+
place, which only cloning does; and those paths parse into an
|
|
173854
|
+
inert document, so their discarded originals never had a queued
|
|
173855
|
+
event to neutralise.
|
|
173856
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
173857
|
+
valid whether we move (drops the trailing entry) or clone (leaves
|
|
173858
|
+
the list intact). */
|
|
173859
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
173860
|
+
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
173861
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
173862
|
+
}
|
|
173863
|
+
}
|
|
173864
|
+
}
|
|
173865
|
+
_forceRemove(currentNode);
|
|
173866
|
+
return true;
|
|
173867
|
+
};
|
|
173149
173868
|
/**
|
|
173150
173869
|
* _sanitizeElements
|
|
173151
173870
|
*
|
|
@@ -173156,7 +173875,6 @@ function createDOMPurify() {
|
|
|
173156
173875
|
* @return true if node was killed, false if left alive
|
|
173157
173876
|
*/
|
|
173158
173877
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
173159
|
-
let content = null;
|
|
173160
173878
|
/* Execute a hook if present */
|
|
173161
173879
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
173162
173880
|
/* Check if element is clobbered or can clobber */
|
|
@@ -173165,76 +173883,41 @@ function createDOMPurify() {
|
|
|
173165
173883
|
return true;
|
|
173166
173884
|
}
|
|
173167
173885
|
/* Now let's check the element's type and name */
|
|
173168
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
173886
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
173169
173887
|
/* Execute a hook if present */
|
|
173170
173888
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
173171
173889
|
tagName,
|
|
173172
173890
|
allowedTags: ALLOWED_TAGS
|
|
173173
173891
|
});
|
|
173174
|
-
/*
|
|
173175
|
-
if (
|
|
173176
|
-
_forceRemove(currentNode);
|
|
173177
|
-
return true;
|
|
173178
|
-
}
|
|
173179
|
-
/* Remove risky CSS construction leading to mXSS */
|
|
173180
|
-
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
173181
|
-
_forceRemove(currentNode);
|
|
173182
|
-
return true;
|
|
173183
|
-
}
|
|
173184
|
-
/* Remove any occurrence of processing instructions */
|
|
173185
|
-
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
173186
|
-
_forceRemove(currentNode);
|
|
173187
|
-
return true;
|
|
173188
|
-
}
|
|
173189
|
-
/* Remove any kind of possibly harmful comments */
|
|
173190
|
-
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
|
|
173892
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
173893
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
173191
173894
|
_forceRemove(currentNode);
|
|
173192
173895
|
return true;
|
|
173193
173896
|
}
|
|
173194
173897
|
/* Remove element if anything forbids its presence */
|
|
173195
173898
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
173196
|
-
|
|
173197
|
-
|
|
173198
|
-
|
|
173199
|
-
|
|
173200
|
-
|
|
173201
|
-
|
|
173202
|
-
|
|
173203
|
-
|
|
173204
|
-
|
|
173205
|
-
|
|
173206
|
-
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
173207
|
-
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
173208
|
-
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
173209
|
-
if (childNodes && parentNode) {
|
|
173210
|
-
const childCount = childNodes.length;
|
|
173211
|
-
for (let i = childCount - 1; i >= 0; --i) {
|
|
173212
|
-
const childClone = cloneNode(childNodes[i], true);
|
|
173213
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
173214
|
-
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
173215
|
-
}
|
|
173216
|
-
}
|
|
173217
|
-
}
|
|
173218
|
-
_forceRemove(currentNode);
|
|
173219
|
-
return true;
|
|
173220
|
-
}
|
|
173221
|
-
/* Check whether element has a valid namespace */
|
|
173222
|
-
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
|
|
173899
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
173900
|
+
}
|
|
173901
|
+
/* Check whether element has a valid namespace.
|
|
173902
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
173903
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
173904
|
+
bound and short-circuits to false for any node minted in a different
|
|
173905
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
173906
|
+
slip past the namespace check entirely. */
|
|
173907
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
173908
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
173223
173909
|
_forceRemove(currentNode);
|
|
173224
173910
|
return true;
|
|
173225
173911
|
}
|
|
173226
173912
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
173227
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
173913
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
173228
173914
|
_forceRemove(currentNode);
|
|
173229
173915
|
return true;
|
|
173230
173916
|
}
|
|
173231
173917
|
/* Sanitize element content to be template-safe */
|
|
173232
173918
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
173233
173919
|
/* Get the element's text content */
|
|
173234
|
-
content = currentNode.textContent;
|
|
173235
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
173236
|
-
content = stringReplace(content, expr, ' ');
|
|
173237
|
-
});
|
|
173920
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
173238
173921
|
if (currentNode.textContent !== content) {
|
|
173239
173922
|
arrayPush(DOMPurify.removed, {
|
|
173240
173923
|
element: currentNode.cloneNode()
|
|
@@ -173264,11 +173947,12 @@ function createDOMPurify() {
|
|
|
173264
173947
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
173265
173948
|
return false;
|
|
173266
173949
|
}
|
|
173950
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
173267
173951
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
173268
173952
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
173269
173953
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
173270
173954
|
We don't need to check the value; it's always URI safe. */
|
|
173271
|
-
if (ALLOW_DATA_ATTR &&
|
|
173955
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
173272
173956
|
if (
|
|
173273
173957
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
173274
173958
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -173280,11 +173964,15 @@ function createDOMPurify() {
|
|
|
173280
173964
|
return false;
|
|
173281
173965
|
}
|
|
173282
173966
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
173283
|
-
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
|
|
173967
|
+
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (value) {
|
|
173284
173968
|
return false;
|
|
173285
173969
|
} else ;
|
|
173286
173970
|
return true;
|
|
173287
173971
|
};
|
|
173972
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
173973
|
+
* never be treated as basic custom elements even when a permissive
|
|
173974
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
173975
|
+
const RESERVED_CUSTOM_ELEMENT_NAMES = addToSet({}, ['annotation-xml', 'color-profile', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'missing-glyph']);
|
|
173288
173976
|
/**
|
|
173289
173977
|
* _isBasicCustomElement
|
|
173290
173978
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -173294,7 +173982,64 @@ function createDOMPurify() {
|
|
|
173294
173982
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
173295
173983
|
*/
|
|
173296
173984
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
173297
|
-
return tagName
|
|
173985
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
173986
|
+
};
|
|
173987
|
+
/**
|
|
173988
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
173989
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
173990
|
+
* unchanged (no TT support yet, see
|
|
173991
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
173992
|
+
*
|
|
173993
|
+
* @param lcTag lowercase tag name of the containing element
|
|
173994
|
+
* @param lcName lowercase attribute name
|
|
173995
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
173996
|
+
* @param value the attribute value to wrap
|
|
173997
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
173998
|
+
*/
|
|
173999
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
174000
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
174001
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
174002
|
+
case 'TrustedHTML':
|
|
174003
|
+
{
|
|
174004
|
+
return _createTrustedHTML(value);
|
|
174005
|
+
}
|
|
174006
|
+
case 'TrustedScriptURL':
|
|
174007
|
+
{
|
|
174008
|
+
return _createTrustedScriptURL(value);
|
|
174009
|
+
}
|
|
174010
|
+
}
|
|
174011
|
+
}
|
|
174012
|
+
return value;
|
|
174013
|
+
};
|
|
174014
|
+
/**
|
|
174015
|
+
* Write a modified attribute value back onto the element. On
|
|
174016
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
174017
|
+
* remove the element when found; otherwise pop the removal entry
|
|
174018
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
174019
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
174020
|
+
* failure, remove the attribute instead.
|
|
174021
|
+
*
|
|
174022
|
+
* @param currentNode the element carrying the attribute
|
|
174023
|
+
* @param name the attribute name as present on the element
|
|
174024
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
174025
|
+
* @param value the new attribute value
|
|
174026
|
+
*/
|
|
174027
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
174028
|
+
try {
|
|
174029
|
+
if (namespaceURI) {
|
|
174030
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
174031
|
+
} else {
|
|
174032
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
174033
|
+
currentNode.setAttribute(name, value);
|
|
174034
|
+
}
|
|
174035
|
+
if (_isClobbered(currentNode)) {
|
|
174036
|
+
_forceRemove(currentNode);
|
|
174037
|
+
} else {
|
|
174038
|
+
arrayPop(DOMPurify.removed);
|
|
174039
|
+
}
|
|
174040
|
+
} catch (_) {
|
|
174041
|
+
_removeAttribute(name, currentNode);
|
|
174042
|
+
}
|
|
173298
174043
|
};
|
|
173299
174044
|
/**
|
|
173300
174045
|
* _sanitizeAttributes
|
|
@@ -173309,9 +174054,7 @@ function createDOMPurify() {
|
|
|
173309
174054
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
173310
174055
|
/* Execute a hook if present */
|
|
173311
174056
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
173312
|
-
const
|
|
173313
|
-
attributes
|
|
173314
|
-
} = currentNode;
|
|
174057
|
+
const attributes = currentNode.attributes;
|
|
173315
174058
|
/* Check if we have attributes; if not we might have a text node */
|
|
173316
174059
|
if (!attributes || _isClobbered(currentNode)) {
|
|
173317
174060
|
return;
|
|
@@ -173324,14 +174067,13 @@ function createDOMPurify() {
|
|
|
173324
174067
|
forceKeepAttr: undefined
|
|
173325
174068
|
};
|
|
173326
174069
|
let l = attributes.length;
|
|
174070
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173327
174071
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
173328
174072
|
while (l--) {
|
|
173329
174073
|
const attr = attributes[l];
|
|
173330
|
-
const
|
|
173331
|
-
|
|
173332
|
-
|
|
173333
|
-
value: attrValue
|
|
173334
|
-
} = attr;
|
|
174074
|
+
const name = attr.name,
|
|
174075
|
+
namespaceURI = attr.namespaceURI,
|
|
174076
|
+
attrValue = attr.value;
|
|
173335
174077
|
const lcName = transformCaseFunc(name);
|
|
173336
174078
|
const initValue = attrValue;
|
|
173337
174079
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -173345,12 +174087,14 @@ function createDOMPurify() {
|
|
|
173345
174087
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
173346
174088
|
* Prefix id and name attributes with `user-content-`
|
|
173347
174089
|
*/
|
|
173348
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
174090
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
173349
174091
|
// Remove the attribute with this value
|
|
173350
174092
|
_removeAttribute(name, currentNode);
|
|
173351
174093
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
173352
174094
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
173353
174095
|
}
|
|
174096
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
174097
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
173354
174098
|
/* Work around a security issue with comments inside attributes */
|
|
173355
174099
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
173356
174100
|
_removeAttribute(name, currentNode);
|
|
@@ -173361,7 +174105,7 @@ function createDOMPurify() {
|
|
|
173361
174105
|
_removeAttribute(name, currentNode);
|
|
173362
174106
|
continue;
|
|
173363
174107
|
}
|
|
173364
|
-
/* Did the hooks
|
|
174108
|
+
/* Did the hooks force-keep the attribute? */
|
|
173365
174109
|
if (hookEvent.forceKeepAttr) {
|
|
173366
174110
|
continue;
|
|
173367
174111
|
}
|
|
@@ -173371,56 +174115,24 @@ function createDOMPurify() {
|
|
|
173371
174115
|
continue;
|
|
173372
174116
|
}
|
|
173373
174117
|
/* Work around a security issue in jQuery 3.0 */
|
|
173374
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
174118
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
173375
174119
|
_removeAttribute(name, currentNode);
|
|
173376
174120
|
continue;
|
|
173377
174121
|
}
|
|
173378
174122
|
/* Sanitize attribute content to be template-safe */
|
|
173379
174123
|
if (SAFE_FOR_TEMPLATES) {
|
|
173380
|
-
|
|
173381
|
-
value = stringReplace(value, expr, ' ');
|
|
173382
|
-
});
|
|
174124
|
+
value = _stripTemplateExpressions(value);
|
|
173383
174125
|
}
|
|
173384
174126
|
/* Is `value` valid for this attribute? */
|
|
173385
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173386
174127
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
173387
174128
|
_removeAttribute(name, currentNode);
|
|
173388
174129
|
continue;
|
|
173389
174130
|
}
|
|
173390
174131
|
/* Handle attributes that require Trusted Types */
|
|
173391
|
-
|
|
173392
|
-
if (namespaceURI) ; else {
|
|
173393
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
173394
|
-
case 'TrustedHTML':
|
|
173395
|
-
{
|
|
173396
|
-
value = trustedTypesPolicy.createHTML(value);
|
|
173397
|
-
break;
|
|
173398
|
-
}
|
|
173399
|
-
case 'TrustedScriptURL':
|
|
173400
|
-
{
|
|
173401
|
-
value = trustedTypesPolicy.createScriptURL(value);
|
|
173402
|
-
break;
|
|
173403
|
-
}
|
|
173404
|
-
}
|
|
173405
|
-
}
|
|
173406
|
-
}
|
|
174132
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
173407
174133
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
173408
174134
|
if (value !== initValue) {
|
|
173409
|
-
|
|
173410
|
-
if (namespaceURI) {
|
|
173411
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
173412
|
-
} else {
|
|
173413
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
173414
|
-
currentNode.setAttribute(name, value);
|
|
173415
|
-
}
|
|
173416
|
-
if (_isClobbered(currentNode)) {
|
|
173417
|
-
_forceRemove(currentNode);
|
|
173418
|
-
} else {
|
|
173419
|
-
arrayPop(DOMPurify.removed);
|
|
173420
|
-
}
|
|
173421
|
-
} catch (_) {
|
|
173422
|
-
_removeAttribute(name, currentNode);
|
|
173423
|
-
}
|
|
174135
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
173424
174136
|
}
|
|
173425
174137
|
}
|
|
173426
174138
|
/* Execute a hook if present */
|
|
@@ -173443,14 +174155,133 @@ function createDOMPurify() {
|
|
|
173443
174155
|
_sanitizeElements(shadowNode);
|
|
173444
174156
|
/* Check attributes next */
|
|
173445
174157
|
_sanitizeAttributes(shadowNode);
|
|
173446
|
-
/* Deep shadow DOM detected
|
|
173447
|
-
|
|
174158
|
+
/* Deep shadow DOM detected.
|
|
174159
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
174160
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
174161
|
+
recurse into <template>.content from foreign realms too. */
|
|
174162
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
173448
174163
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
173449
174164
|
}
|
|
174165
|
+
/* An element iterated here may itself host an attached
|
|
174166
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
174167
|
+
trees, so a shadow root nested inside template.content was
|
|
174168
|
+
previously reached by no walk at all (the pre-pass at
|
|
174169
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
174170
|
+
doesn't enter template.content; the template-content recursion
|
|
174171
|
+
above iterates the content but never inspected shadowRoot).
|
|
174172
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
174173
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
174174
|
+
iterator also surfaces. */
|
|
174175
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
174176
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
174177
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
174178
|
+
if (_isDocumentFragment(innerSr)) {
|
|
174179
|
+
_sanitizeAttachedShadowRoots(innerSr);
|
|
174180
|
+
_sanitizeShadowDOM2(innerSr);
|
|
174181
|
+
}
|
|
174182
|
+
}
|
|
173450
174183
|
}
|
|
173451
174184
|
/* Execute a hook if present */
|
|
173452
174185
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
173453
174186
|
};
|
|
174187
|
+
/**
|
|
174188
|
+
* _sanitizeAttachedShadowRoots
|
|
174189
|
+
*
|
|
174190
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
174191
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
174192
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
174193
|
+
* shadow root would otherwise be skipped entirely.
|
|
174194
|
+
*
|
|
174195
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
174196
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
174197
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
174198
|
+
* shadow root because it was created with `clonable: true`.
|
|
174199
|
+
*
|
|
174200
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
174201
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
174202
|
+
* untouched — string-input paths are not affected.
|
|
174203
|
+
*
|
|
174204
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
174205
|
+
*/
|
|
174206
|
+
const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
|
|
174207
|
+
/* Iterative (explicit stack) rather than per-child recursion. DOM APIs
|
|
174208
|
+
impose no depth cap, so an attacker-shaped tree (JSON/CRDT/editor data
|
|
174209
|
+
built straight into the DOM — the IN_PLACE surface) deeper than the JS
|
|
174210
|
+
call-stack budget would otherwise overflow native recursion here and
|
|
174211
|
+
throw at the IN_PLACE entry pre-pass, before a single node is
|
|
174212
|
+
sanitized, leaving the caller's live tree untouched (fail-open). See
|
|
174213
|
+
campaign-3 F4. A heap stack keeps depth off the call stack.
|
|
174214
|
+
Each work item is either a node to descend into, or a deferred
|
|
174215
|
+
`_sanitizeShadowDOM` for an already-walked shadow root. The deferred
|
|
174216
|
+
form preserves the original post-order discipline: a shadow root's
|
|
174217
|
+
nested shadow roots are discovered before the outer shadow is
|
|
174218
|
+
sanitized (which may remove hosts). Pushes are in reverse of the
|
|
174219
|
+
desired processing order (LIFO): template content, then children, then
|
|
174220
|
+
the shadow-sanitize, then the shadow walk — so the order matches the
|
|
174221
|
+
previous recursion exactly. */
|
|
174222
|
+
const stack = [{
|
|
174223
|
+
node: root,
|
|
174224
|
+
shadow: null
|
|
174225
|
+
}];
|
|
174226
|
+
while (stack.length > 0) {
|
|
174227
|
+
const item = stack.pop();
|
|
174228
|
+
/* Deferred shadow-DOM sanitisation: runs after its subtree was walked. */
|
|
174229
|
+
if (item.shadow) {
|
|
174230
|
+
_sanitizeShadowDOM2(item.shadow);
|
|
174231
|
+
continue;
|
|
174232
|
+
}
|
|
174233
|
+
const node = item.node;
|
|
174234
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
174235
|
+
const isElement = nodeType === NODE_TYPE.element;
|
|
174236
|
+
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
174237
|
+
the first child is processed first. Snapshotting matters because a
|
|
174238
|
+
hook may detach siblings mid-walk. */
|
|
174239
|
+
const childNodes = getChildNodes(node);
|
|
174240
|
+
if (childNodes) {
|
|
174241
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
174242
|
+
stack.push({
|
|
174243
|
+
node: childNodes[i],
|
|
174244
|
+
shadow: null
|
|
174245
|
+
});
|
|
174246
|
+
}
|
|
174247
|
+
}
|
|
174248
|
+
/* (pushed before children → processed after them, matching the old
|
|
174249
|
+
"template content last" order) When the node is a <template>,
|
|
174250
|
+
descend into its content. */
|
|
174251
|
+
if (isElement) {
|
|
174252
|
+
const rootName = getNodeName ? getNodeName(node) : null;
|
|
174253
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
174254
|
+
const content = node.content;
|
|
174255
|
+
if (_isDocumentFragment(content)) {
|
|
174256
|
+
stack.push({
|
|
174257
|
+
node: content,
|
|
174258
|
+
shadow: null
|
|
174259
|
+
});
|
|
174260
|
+
}
|
|
174261
|
+
}
|
|
174262
|
+
}
|
|
174263
|
+
/* Shadow root (processed first): walk its subtree, then sanitise it.
|
|
174264
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
174265
|
+
rather than `instanceof DocumentFragment`, which is realm-bound and
|
|
174266
|
+
silently skipped foreign-realm shadow roots (e.g.
|
|
174267
|
+
iframe.contentDocument attachShadow). */
|
|
174268
|
+
if (isElement) {
|
|
174269
|
+
const sr = getShadowRoot(node);
|
|
174270
|
+
if (_isDocumentFragment(sr)) {
|
|
174271
|
+
/* Push the deferred sanitise first so it pops after the shadow
|
|
174272
|
+
walk we push next, i.e. nested shadow roots are discovered
|
|
174273
|
+
before this one is sanitised. */
|
|
174274
|
+
stack.push({
|
|
174275
|
+
node: null,
|
|
174276
|
+
shadow: sr
|
|
174277
|
+
}, {
|
|
174278
|
+
node: sr,
|
|
174279
|
+
shadow: null
|
|
174280
|
+
});
|
|
174281
|
+
}
|
|
174282
|
+
}
|
|
174283
|
+
}
|
|
174284
|
+
};
|
|
173454
174285
|
// eslint-disable-next-line complexity
|
|
173455
174286
|
DOMPurify.sanitize = function (dirty) {
|
|
173456
174287
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -173467,13 +174298,9 @@ function createDOMPurify() {
|
|
|
173467
174298
|
}
|
|
173468
174299
|
/* Stringify, in case dirty is an object */
|
|
173469
174300
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
173470
|
-
|
|
173471
|
-
|
|
173472
|
-
|
|
173473
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
173474
|
-
}
|
|
173475
|
-
} else {
|
|
173476
|
-
throw typeErrorCreate('toString is not a function');
|
|
174301
|
+
dirty = stringifyValue(dirty);
|
|
174302
|
+
if (typeof dirty !== 'string') {
|
|
174303
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
173477
174304
|
}
|
|
173478
174305
|
}
|
|
173479
174306
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -173481,24 +174308,78 @@ function createDOMPurify() {
|
|
|
173481
174308
|
return dirty;
|
|
173482
174309
|
}
|
|
173483
174310
|
/* Assign config vars */
|
|
173484
|
-
if (
|
|
174311
|
+
if (SET_CONFIG) {
|
|
174312
|
+
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
|
|
174313
|
+
* not re-derived per call. Restore them from the pristine bindings
|
|
174314
|
+
* captured at setConfig() time so a previous call's hook clone (mutated
|
|
174315
|
+
* below) does not carry over. */
|
|
174316
|
+
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
|
|
174317
|
+
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
|
|
174318
|
+
} else {
|
|
173485
174319
|
_parseConfig(cfg);
|
|
173486
174320
|
}
|
|
174321
|
+
/* Clone the hook-mutable allowlists before the walk whenever an
|
|
174322
|
+
* uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
|
|
174323
|
+
* and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
|
|
174324
|
+
* a hook that widens them would otherwise mutate the shared set
|
|
174325
|
+
* permanently: across later calls and across every element. Cloning per
|
|
174326
|
+
* walk keeps documented in-call widening working while scoping it to the
|
|
174327
|
+
* call. A single guard for both config paths - the per-call path rebinds
|
|
174328
|
+
* the sets in _parseConfig each call, the persistent path restores them
|
|
174329
|
+
* from the captured bindings just above - so the two cannot diverge. */
|
|
174330
|
+
if (hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) {
|
|
174331
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
174332
|
+
}
|
|
174333
|
+
if (hooks.uponSanitizeAttribute.length > 0) {
|
|
174334
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
174335
|
+
}
|
|
173487
174336
|
/* Clean up removed elements */
|
|
173488
174337
|
DOMPurify.removed = [];
|
|
173489
|
-
/*
|
|
173490
|
-
|
|
173491
|
-
|
|
173492
|
-
|
|
173493
|
-
|
|
173494
|
-
|
|
173495
|
-
|
|
173496
|
-
|
|
174338
|
+
/* Resolve IN_PLACE for this call without mutating persistent config.
|
|
174339
|
+
Writing the IN_PLACE closure variable here leaks under setConfig(),
|
|
174340
|
+
where _parseConfig is skipped on later calls: a single string call would
|
|
174341
|
+
disable in-place mode for every subsequent node call, returning a
|
|
174342
|
+
sanitized copy while leaving the caller's node — which in-place callers
|
|
174343
|
+
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
174344
|
+
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
174345
|
+
if (inPlace) {
|
|
174346
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
174347
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
174348
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
174349
|
+
the property and let this check skip the root-allowlist
|
|
174350
|
+
validation entirely. */
|
|
174351
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
174352
|
+
if (typeof nn === 'string') {
|
|
174353
|
+
const tagName = transformCaseFunc(nn);
|
|
173497
174354
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
173498
174355
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
173499
174356
|
}
|
|
173500
174357
|
}
|
|
173501
|
-
|
|
174358
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
174359
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
174360
|
+
falls through to Element.prototype.remove(), which per spec
|
|
174361
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
174362
|
+
then survive the main loop with its attributes uninspected,
|
|
174363
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
174364
|
+
result would be an attacker-controlled form, complete with any
|
|
174365
|
+
event-handler attributes the caller passed in, handed back to
|
|
174366
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
174367
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
174368
|
+
if (_isClobbered(dirty)) {
|
|
174369
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
174370
|
+
}
|
|
174371
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
174372
|
+
The iterator does not descend into shadow trees. Same fail-closed
|
|
174373
|
+
barrier as the main walk (campaign-3 F2): a custom-element reaction
|
|
174374
|
+
inside a shadow root could abort this pre-pass before the walk runs,
|
|
174375
|
+
which would otherwise leave the entire live tree unsanitized. */
|
|
174376
|
+
try {
|
|
174377
|
+
_sanitizeAttachedShadowRoots(dirty);
|
|
174378
|
+
} catch (error) {
|
|
174379
|
+
_neutralizeRoot(dirty);
|
|
174380
|
+
throw error;
|
|
174381
|
+
}
|
|
174382
|
+
} else if (_isNode(dirty)) {
|
|
173502
174383
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
173503
174384
|
elements being stripped by the parser */
|
|
173504
174385
|
body = _initDocument('<!---->');
|
|
@@ -173512,12 +174393,18 @@ function createDOMPurify() {
|
|
|
173512
174393
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
173513
174394
|
body.appendChild(importedNode);
|
|
173514
174395
|
}
|
|
174396
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
174397
|
+
them before the main iterator runs, since the iterator does not
|
|
174398
|
+
descend into shadow trees. The walk routes every read through a
|
|
174399
|
+
cached prototype getter so clobbering descendants on a form root
|
|
174400
|
+
cannot hide a shadow host from this pass. */
|
|
174401
|
+
_sanitizeAttachedShadowRoots(importedNode);
|
|
173515
174402
|
} else {
|
|
173516
174403
|
/* Exit directly if we have nothing to do */
|
|
173517
174404
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
173518
174405
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
173519
174406
|
dirty.indexOf('<') === -1) {
|
|
173520
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174407
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
173521
174408
|
}
|
|
173522
174409
|
/* Initialize the document to work on */
|
|
173523
174410
|
body = _initDocument(dirty);
|
|
@@ -173531,31 +174418,59 @@ function createDOMPurify() {
|
|
|
173531
174418
|
_forceRemove(body.firstChild);
|
|
173532
174419
|
}
|
|
173533
174420
|
/* Get node iterator */
|
|
173534
|
-
const nodeIterator = _createNodeIterator(
|
|
173535
|
-
/* Now start iterating over the created document
|
|
173536
|
-
|
|
173537
|
-
|
|
173538
|
-
|
|
173539
|
-
|
|
173540
|
-
|
|
173541
|
-
|
|
173542
|
-
|
|
173543
|
-
|
|
174421
|
+
const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
|
|
174422
|
+
/* Now start iterating over the created document.
|
|
174423
|
+
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
174424
|
+
engine/custom-element mutation can detach a node mid-walk so
|
|
174425
|
+
`_forceRemove`'s parentless guard throws, aborting the loop. Without the
|
|
174426
|
+
barrier the caller's in-place tree would be left half-sanitized with the
|
|
174427
|
+
unvisited tail still armed. On any throw we fail closed — strip the
|
|
174428
|
+
in-place root bare — then rethrow so the existing throw contract is
|
|
174429
|
+
preserved. (String/DOM-copy paths never return the partial body, so the
|
|
174430
|
+
propagating throw is already fail-closed there.) */
|
|
174431
|
+
try {
|
|
174432
|
+
while (currentNode = nodeIterator.nextNode()) {
|
|
174433
|
+
/* Sanitize tags and elements */
|
|
174434
|
+
_sanitizeElements(currentNode);
|
|
174435
|
+
/* Check attributes next */
|
|
174436
|
+
_sanitizeAttributes(currentNode);
|
|
174437
|
+
/* Shadow DOM detected, sanitize it.
|
|
174438
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
174439
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
174440
|
+
walked correctly. */
|
|
174441
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
174442
|
+
_sanitizeShadowDOM2(currentNode.content);
|
|
174443
|
+
}
|
|
174444
|
+
}
|
|
174445
|
+
} catch (error) {
|
|
174446
|
+
if (inPlace) {
|
|
174447
|
+
_neutralizeRoot(dirty);
|
|
173544
174448
|
}
|
|
174449
|
+
throw error;
|
|
173545
174450
|
}
|
|
173546
174451
|
/* If we sanitized `dirty` in-place, return it. */
|
|
173547
|
-
if (
|
|
174452
|
+
if (inPlace) {
|
|
174453
|
+
/* Fail-closed completion of the audit-5 F1 fix: every node removed from
|
|
174454
|
+
the caller's live tree is detached but may still hold a queued
|
|
174455
|
+
resource-event handler that fires in page scope after we return. The
|
|
174456
|
+
move-hoist covers only disallowed-tag KEEP_CONTENT removals; strip the
|
|
174457
|
+
non-allow-listed attributes off every other removed subtree (clobber,
|
|
174458
|
+
mXSS, namespace, comments, KEEP_CONTENT:false, …) so those handlers are
|
|
174459
|
+
cancelled before any event can fire. Runs synchronously, pre-return. */
|
|
174460
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
174461
|
+
if (entry.element) {
|
|
174462
|
+
_neutralizeSubtree(entry.element);
|
|
174463
|
+
}
|
|
174464
|
+
});
|
|
174465
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
174466
|
+
_scrubTemplateExpressions2(dirty);
|
|
174467
|
+
}
|
|
173548
174468
|
return dirty;
|
|
173549
174469
|
}
|
|
173550
174470
|
/* Return sanitized string or DOM */
|
|
173551
174471
|
if (RETURN_DOM) {
|
|
173552
174472
|
if (SAFE_FOR_TEMPLATES) {
|
|
173553
|
-
body
|
|
173554
|
-
let html = body.innerHTML;
|
|
173555
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
173556
|
-
html = stringReplace(html, expr, ' ');
|
|
173557
|
-
});
|
|
173558
|
-
body.innerHTML = html;
|
|
174473
|
+
_scrubTemplateExpressions2(body);
|
|
173559
174474
|
}
|
|
173560
174475
|
if (RETURN_DOM_FRAGMENT) {
|
|
173561
174476
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -173585,20 +174500,28 @@ function createDOMPurify() {
|
|
|
173585
174500
|
}
|
|
173586
174501
|
/* Sanitize final string template-safe */
|
|
173587
174502
|
if (SAFE_FOR_TEMPLATES) {
|
|
173588
|
-
|
|
173589
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
173590
|
-
});
|
|
174503
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
173591
174504
|
}
|
|
173592
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174505
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
173593
174506
|
};
|
|
173594
174507
|
DOMPurify.setConfig = function () {
|
|
173595
174508
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
173596
174509
|
_parseConfig(cfg);
|
|
173597
174510
|
SET_CONFIG = true;
|
|
174511
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
174512
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
173598
174513
|
};
|
|
173599
174514
|
DOMPurify.clearConfig = function () {
|
|
173600
174515
|
CONFIG = null;
|
|
173601
174516
|
SET_CONFIG = false;
|
|
174517
|
+
SET_CONFIG_ALLOWED_TAGS = null;
|
|
174518
|
+
SET_CONFIG_ALLOWED_ATTR = null;
|
|
174519
|
+
// Drop any caller-supplied Trusted Types policy so it cannot poison later
|
|
174520
|
+
// `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
|
|
174521
|
+
// never recreated — Trusted Types throws on duplicate names) is restored by
|
|
174522
|
+
// the next `_parseConfig`. See GHSA-vxr8-fq34-vvx9.
|
|
174523
|
+
trustedTypesPolicy = defaultTrustedTypesPolicy;
|
|
174524
|
+
emptyHTML = '';
|
|
173602
174525
|
};
|
|
173603
174526
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
173604
174527
|
/* Initialize shared config vars if necessary. */
|
|
@@ -173613,9 +174536,19 @@ function createDOMPurify() {
|
|
|
173613
174536
|
if (typeof hookFunction !== 'function') {
|
|
173614
174537
|
return;
|
|
173615
174538
|
}
|
|
174539
|
+
/* Reject unknown entry points. Without this, a non-hook key (e.g.
|
|
174540
|
+
* '__proto__') indexes off the prototype chain rather than a real
|
|
174541
|
+
* hook array, and arrayPush then writes to Object.prototype. Guard
|
|
174542
|
+
* with an own-property check against the known hook names. */
|
|
174543
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174544
|
+
return;
|
|
174545
|
+
}
|
|
173616
174546
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
173617
174547
|
};
|
|
173618
174548
|
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
174549
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174550
|
+
return undefined;
|
|
174551
|
+
}
|
|
173619
174552
|
if (hookFunction !== undefined) {
|
|
173620
174553
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
173621
174554
|
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
@@ -173623,6 +174556,9 @@ function createDOMPurify() {
|
|
|
173623
174556
|
return arrayPop(hooks[entryPoint]);
|
|
173624
174557
|
};
|
|
173625
174558
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
174559
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174560
|
+
return;
|
|
174561
|
+
}
|
|
173626
174562
|
hooks[entryPoint] = [];
|
|
173627
174563
|
};
|
|
173628
174564
|
DOMPurify.removeAllHooks = function () {
|