@cognigy/webchat 3.46.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 +1283 -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";
|
|
@@ -38210,10 +38210,20 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38210
38210
|
/**
|
|
38211
38211
|
* Emits the `webchat/close-button` analytics event.
|
|
38212
38212
|
*
|
|
38213
|
-
* Fired
|
|
38214
|
-
* (`handleCloseAndReset`)
|
|
38215
|
-
* NOT emitted for the toggle button,
|
|
38216
|
-
* 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.
|
|
38217
38227
|
*
|
|
38218
38228
|
* This lives in the component rather than the redux analytics middleware
|
|
38219
38229
|
* (where `webchat/open|close|minimize` are emitted) on purpose: the store only
|
|
@@ -38221,7 +38231,10 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38221
38231
|
* the close, so the originating interaction is only known here.
|
|
38222
38232
|
*/
|
|
38223
38233
|
emitCloseButtonAnalytics = () => {
|
|
38224
|
-
this.props.onEmitAnalytics("webchat/close-button"
|
|
38234
|
+
this.props.onEmitAnalytics("webchat/close-button", {
|
|
38235
|
+
connected: this.props.connected,
|
|
38236
|
+
hadConnection: this.state.hadConnection
|
|
38237
|
+
});
|
|
38225
38238
|
};
|
|
38226
38239
|
render() {
|
|
38227
38240
|
const {
|
|
@@ -38477,6 +38490,17 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38477
38490
|
// Restore focus to chat toggle button
|
|
38478
38491
|
this.chatToggleButtonRef?.current?.focus?.();
|
|
38479
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
|
+
};
|
|
38480
38504
|
const handleOnGoBack = () => {
|
|
38481
38505
|
if (!showChatOptionsScreen && !showRatingScreen) {
|
|
38482
38506
|
onSetShowPrevConversations(false);
|
|
@@ -38636,7 +38660,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38636
38660
|
onSetShowHomeScreen: onSetShowHomeScreen,
|
|
38637
38661
|
onStartConversation: this.handleStartConversation,
|
|
38638
38662
|
onSetShowPrevConversations: onSetShowPrevConversations,
|
|
38639
|
-
onClose:
|
|
38663
|
+
onClose: handleHomeScreenClose,
|
|
38640
38664
|
config: config,
|
|
38641
38665
|
onEmitAnalytics: onEmitAnalytics,
|
|
38642
38666
|
onSendActionButtonMessage: this.handleSendActionButtonMessage
|
|
@@ -44704,6 +44728,22 @@ class Webchat extends react__WEBPACK_IMPORTED_MODULE_0__.PureComponent {
|
|
|
44704
44728
|
connect = async () => {
|
|
44705
44729
|
this.store.dispatch((0,_store_connection_connection_middleware__WEBPACK_IMPORTED_MODULE_7__/* .connect */ .Ng)());
|
|
44706
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
|
+
};
|
|
44707
44747
|
sendMessage = (text, data, options) => {
|
|
44708
44748
|
this.store.dispatch((0,_store_messages_message_middleware__WEBPACK_IMPORTED_MODULE_4__/* .sendMessage */ ._z)({
|
|
44709
44749
|
text,
|
|
@@ -56925,6 +56965,23 @@ a._button_19rd2_1._disabled_19rd2_52:focus {
|
|
|
56925
56965
|
height: 100%;
|
|
56926
56966
|
border-right: 2px solid var(--cc-primary-contrast-color);
|
|
56927
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
|
+
}
|
|
56928
56985
|
._buttons_tq8ye_1 {
|
|
56929
56986
|
align-items: flex-start;
|
|
56930
56987
|
display: flex;
|
|
@@ -57597,23 +57654,6 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57597
57654
|
._mainButtonWrapper_zhv80_144 {
|
|
57598
57655
|
padding: 16px;
|
|
57599
57656
|
}
|
|
57600
|
-
._srOnly_14hnz_1 {
|
|
57601
|
-
position: absolute;
|
|
57602
|
-
width: 1px;
|
|
57603
|
-
height: 1px;
|
|
57604
|
-
padding: 0;
|
|
57605
|
-
margin: -1px;
|
|
57606
|
-
overflow: hidden;
|
|
57607
|
-
clip: rect(0, 0, 0, 0);
|
|
57608
|
-
white-space: nowrap;
|
|
57609
|
-
border: 0;
|
|
57610
|
-
}
|
|
57611
|
-
|
|
57612
|
-
._divider_14hnz_13 {
|
|
57613
|
-
border-bottom-width: 1px;
|
|
57614
|
-
border-bottom-style: solid;
|
|
57615
|
-
border-bottom-color: var(--cc-black-80);
|
|
57616
|
-
}
|
|
57617
57657
|
._slideItem_1prat_1 {
|
|
57618
57658
|
position: relative;
|
|
57619
57659
|
width: 206px;
|
|
@@ -58933,13 +58973,17 @@ article ._header_21mid_1 ._avatar-name_21mid_27 {
|
|
|
58933
58973
|
margin: 0;
|
|
58934
58974
|
padding: 0;
|
|
58935
58975
|
}
|
|
58936
|
-
article .
|
|
58976
|
+
article ._avatar_o5ge0_1 {
|
|
58937
58977
|
border-radius: 1000px;
|
|
58938
58978
|
width: 28px;
|
|
58939
58979
|
height: 28px;
|
|
58940
|
-
background-color: var(--cc-primary-color);
|
|
58941
58980
|
box-sizing: border-box;
|
|
58942
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
|
+
}
|
|
58943
58987
|
:root {
|
|
58944
58988
|
--cc-font-family: var(--webchat-font-family, Figtree);
|
|
58945
58989
|
--cc-action-button-border-radius: var(--webchat-action-button-border-radius, 19px);
|
|
@@ -85274,11 +85318,16 @@ const Text = props2 => {
|
|
|
85274
85318
|
a: ({
|
|
85275
85319
|
node: _node,
|
|
85276
85320
|
...props22
|
|
85277
|
-
}) =>
|
|
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", {
|
|
85278
85327
|
target: "_blank",
|
|
85279
85328
|
rel: "noreferrer",
|
|
85280
85329
|
...props22
|
|
85281
|
-
}),
|
|
85330
|
+
})),
|
|
85282
85331
|
p: ({
|
|
85283
85332
|
node: _node,
|
|
85284
85333
|
children,
|
|
@@ -85928,6 +85977,12 @@ const classes$e = {
|
|
|
85928
85977
|
imageDisabled,
|
|
85929
85978
|
buttonImageContainer
|
|
85930
85979
|
};
|
|
85980
|
+
const srOnly = "_srOnly_14hnz_1";
|
|
85981
|
+
const divider = "_divider_14hnz_13";
|
|
85982
|
+
const mainClasses = {
|
|
85983
|
+
srOnly,
|
|
85984
|
+
divider
|
|
85985
|
+
};
|
|
85931
85986
|
const ActionButton = props2 => {
|
|
85932
85987
|
const {
|
|
85933
85988
|
button: button2,
|
|
@@ -85950,32 +86005,29 @@ const ActionButton = props2 => {
|
|
|
85950
86005
|
const customAllowedHtmlTags = config2?.settings?.widgetSettings?.customAllowedHtmlTags;
|
|
85951
86006
|
const __html = config2?.settings?.layout?.disableHtmlContentSanitization ? buttonLabel : sanitizeHTMLWithConfig(buttonLabel, customAllowedHtmlTags);
|
|
85952
86007
|
const isPhoneNumber = button2.payload && (buttonType === "phone_number" || buttonType === "user_phone_number");
|
|
85953
|
-
const buttonTitle = button2.title || "";
|
|
85954
86008
|
const isWebURL = "type" in button2 && button2.type === "web_url";
|
|
85955
86009
|
const isWebURLButtonTargetBlank = isWebURL && button2.target !== "_self";
|
|
85956
86010
|
const opensInNewTabLabel = config2?.settings?.customTranslations?.ariaLabels?.opensInNewTab || "Opens in new tab";
|
|
85957
|
-
const
|
|
85958
|
-
|
|
85959
|
-
|
|
85960
|
-
|
|
85961
|
-
|
|
85962
|
-
|
|
85963
|
-
|
|
85964
|
-
|
|
85965
|
-
|
|
85966
|
-
} else if (total <= 1 && isURLInNewTab) {
|
|
85967
|
-
return newTabURLButtonTitle;
|
|
85968
|
-
}
|
|
85969
|
-
};
|
|
85970
|
-
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", {
|
|
85971
86020
|
...props22,
|
|
85972
86021
|
href: `tel:${button2.payload}`
|
|
85973
|
-
}) : null;
|
|
85974
|
-
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", {
|
|
85975
86027
|
...props22,
|
|
85976
86028
|
href: button2.url,
|
|
85977
86029
|
target: button2.target
|
|
85978
|
-
}) : null;
|
|
86030
|
+
}) : null);
|
|
85979
86031
|
const Button2 = props22 => /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
|
|
85980
86032
|
...props22,
|
|
85981
86033
|
disabled: disabled2
|
|
@@ -86029,10 +86081,12 @@ const ActionButton = props2 => {
|
|
|
86029
86081
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(Component, {
|
|
86030
86082
|
onClick: onClick2,
|
|
86031
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"),
|
|
86032
|
-
"aria-label": getAriaLabel(),
|
|
86033
86084
|
"aria-disabled": disabled2,
|
|
86034
86085
|
tabIndex: disabled2 ? -1 : 0,
|
|
86035
|
-
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", {
|
|
86036
86090
|
className: classes$e.buttonImageContainer,
|
|
86037
86091
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
86038
86092
|
src: buttonImage2,
|
|
@@ -86046,7 +86100,10 @@ const ActionButton = props2 => {
|
|
|
86046
86100
|
__html
|
|
86047
86101
|
},
|
|
86048
86102
|
className: !!buttonImage2 && classes$e.buttonLabelWithImage
|
|
86049
|
-
}), renderIcon()
|
|
86103
|
+
}), renderIcon(), isWebURL && isWebURLButtonTargetBlank && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
86104
|
+
className: mainClasses.srOnly,
|
|
86105
|
+
children: `, ${opensInNewTabLabel}`
|
|
86106
|
+
})]
|
|
86050
86107
|
});
|
|
86051
86108
|
};
|
|
86052
86109
|
const buttons$1 = "_buttons_tq8ye_1";
|
|
@@ -92731,7 +92788,12 @@ const Controls = props2 => {
|
|
|
92731
92788
|
})]
|
|
92732
92789
|
})
|
|
92733
92790
|
})]
|
|
92734
|
-
}), 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", {
|
|
92735
92797
|
ref: downloadTranscriptLinkRef,
|
|
92736
92798
|
href: `data:text/plain;charset=utf-8,${encodeURIComponent(altText)}`,
|
|
92737
92799
|
download: "audio-transcript.txt",
|
|
@@ -92993,12 +93055,6 @@ const classes$9 = {
|
|
|
92993
93055
|
listItemButtonWrapper,
|
|
92994
93056
|
mainButtonWrapper
|
|
92995
93057
|
};
|
|
92996
|
-
const srOnly = "_srOnly_14hnz_1";
|
|
92997
|
-
const divider = "_divider_14hnz_13";
|
|
92998
|
-
const mainClasses = {
|
|
92999
|
-
srOnly,
|
|
93000
|
-
divider
|
|
93001
|
-
};
|
|
93002
93058
|
const ListItem = props2 => {
|
|
93003
93059
|
const {
|
|
93004
93060
|
action,
|
|
@@ -93059,9 +93115,11 @@ const ListItem = props2 => {
|
|
|
93059
93115
|
backgroundImage: getBackgroundImage(image_url)
|
|
93060
93116
|
},
|
|
93061
93117
|
"data-testid": "regular-image",
|
|
93062
|
-
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", {
|
|
93063
93119
|
role: "img",
|
|
93064
93120
|
"aria-label": image_alt_text
|
|
93121
|
+
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93122
|
+
"aria-hidden": "true"
|
|
93065
93123
|
})
|
|
93066
93124
|
});
|
|
93067
93125
|
}, [image_alt_text, image_url]);
|
|
@@ -118672,7 +118730,12 @@ const DatePicker = () => {
|
|
|
118672
118730
|
"aria-controls": `webchat-plugin-date-picker-${message2.id}`,
|
|
118673
118731
|
"aria-haspopup": "dialog",
|
|
118674
118732
|
children: openText
|
|
118675
|
-
}), 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", {
|
|
118676
118739
|
ref: datePickerRef,
|
|
118677
118740
|
id: `webchat-plugin-date-picker-${message2.id}`,
|
|
118678
118741
|
className: classnames$1(classes$6.wrapper, "webchat-plugin-date-picker"),
|
|
@@ -134729,7 +134792,7 @@ const classes$3 = {
|
|
|
134729
134792
|
timestamp,
|
|
134730
134793
|
"avatar-name": "_avatar-name_21mid_27"
|
|
134731
134794
|
};
|
|
134732
|
-
const avatar = "
|
|
134795
|
+
const avatar = "_avatar_o5ge0_1";
|
|
134733
134796
|
const classes$2 = {
|
|
134734
134797
|
avatar
|
|
134735
134798
|
};
|
|
@@ -134758,11 +134821,13 @@ const Avatar = props2 => {
|
|
|
134758
134821
|
setAvatarUrl(botAvatar);
|
|
134759
134822
|
}
|
|
134760
134823
|
}, [message2.avatarUrl, message2?.source, overrides.agentAvatarOverrideUrlOnce, overrides.botAvatarOverrideUrlOnce]);
|
|
134824
|
+
const isDefaultAvatar = avatarUrl === botAvatar || avatarUrl === placeholderAvatar;
|
|
134761
134825
|
const classNames = classnames$1(classes$2.avatar, props2.className, "webchat-avatar", message2?.source);
|
|
134762
134826
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
134763
134827
|
alt: "",
|
|
134764
134828
|
className: classNames,
|
|
134765
134829
|
src: avatarUrl,
|
|
134830
|
+
"data-default-avatar": isDefaultAvatar ? "" : void 0,
|
|
134766
134831
|
onError: () => setAvatarUrl(placeholderAvatar),
|
|
134767
134832
|
"data-testid": `${message2?.source || "unknown source"}-avatar`
|
|
134768
134833
|
});
|
|
@@ -172256,24 +172321,65 @@ function isUrl(fileUrlOrPath) {
|
|
|
172256
172321
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
172257
172322
|
/* harmony export */ A: () => (/* binding */ purify)
|
|
172258
172323
|
/* harmony export */ });
|
|
172259
|
-
/*! @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 */
|
|
172260
172325
|
|
|
172261
|
-
|
|
172262
|
-
|
|
172263
|
-
|
|
172264
|
-
|
|
172265
|
-
|
|
172266
|
-
|
|
172267
|
-
|
|
172268
|
-
|
|
172269
|
-
|
|
172270
|
-
|
|
172271
|
-
|
|
172272
|
-
|
|
172273
|
-
|
|
172274
|
-
|
|
172275
|
-
|
|
172276
|
-
|
|
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;
|
|
172277
172383
|
if (!freeze) {
|
|
172278
172384
|
freeze = function freeze(x) {
|
|
172279
172385
|
return x;
|
|
@@ -172305,13 +172411,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
|
172305
172411
|
const arrayPop = unapply(Array.prototype.pop);
|
|
172306
172412
|
const arrayPush = unapply(Array.prototype.push);
|
|
172307
172413
|
const arraySplice = unapply(Array.prototype.splice);
|
|
172414
|
+
const arrayIsArray = Array.isArray;
|
|
172308
172415
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
172309
172416
|
const stringToString = unapply(String.prototype.toString);
|
|
172310
172417
|
const stringMatch = unapply(String.prototype.match);
|
|
172311
172418
|
const stringReplace = unapply(String.prototype.replace);
|
|
172312
172419
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
172313
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);
|
|
172314
172425
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
172426
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
172315
172427
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
172316
172428
|
const typeErrorCreate = unconstruct(TypeError);
|
|
172317
172429
|
/**
|
|
@@ -172361,6 +172473,9 @@ function addToSet(set, array) {
|
|
|
172361
172473
|
// Prevent prototype setters from intercepting set as a this value.
|
|
172362
172474
|
setPrototypeOf(set, null);
|
|
172363
172475
|
}
|
|
172476
|
+
if (!arrayIsArray(array)) {
|
|
172477
|
+
return set;
|
|
172478
|
+
}
|
|
172364
172479
|
let l = array.length;
|
|
172365
172480
|
while (l--) {
|
|
172366
172481
|
let element = array[l];
|
|
@@ -172401,10 +172516,13 @@ function cleanArray(array) {
|
|
|
172401
172516
|
*/
|
|
172402
172517
|
function clone(object) {
|
|
172403
172518
|
const newObject = create(null);
|
|
172404
|
-
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];
|
|
172405
172523
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
172406
172524
|
if (isPropertyExist) {
|
|
172407
|
-
if (
|
|
172525
|
+
if (arrayIsArray(value)) {
|
|
172408
172526
|
newObject[property] = cleanArray(value);
|
|
172409
172527
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
172410
172528
|
newObject[property] = clone(value);
|
|
@@ -172415,6 +172533,58 @@ function clone(object) {
|
|
|
172415
172533
|
}
|
|
172416
172534
|
return newObject;
|
|
172417
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
|
+
}
|
|
172418
172588
|
/**
|
|
172419
172589
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
172420
172590
|
*
|
|
@@ -172440,6 +172610,14 @@ function lookupGetter(object, prop) {
|
|
|
172440
172610
|
}
|
|
172441
172611
|
return fallbackValue;
|
|
172442
172612
|
}
|
|
172613
|
+
function isRegex(value) {
|
|
172614
|
+
try {
|
|
172615
|
+
regExpTest(value, '');
|
|
172616
|
+
return true;
|
|
172617
|
+
} catch (_unused) {
|
|
172618
|
+
return false;
|
|
172619
|
+
}
|
|
172620
|
+
}
|
|
172443
172621
|
|
|
172444
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']);
|
|
172445
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']);
|
|
@@ -172455,15 +172633,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
172455
172633
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
172456
172634
|
const text = freeze(['#text']);
|
|
172457
172635
|
|
|
172458
|
-
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']);
|
|
172459
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']);
|
|
172460
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']);
|
|
172461
172639
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
172462
172640
|
|
|
172463
|
-
|
|
172464
|
-
const
|
|
172465
|
-
const
|
|
172466
|
-
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);
|
|
172467
172644
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
172468
172645
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
172469
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
|
|
@@ -172473,30 +172650,31 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
172473
172650
|
);
|
|
172474
172651
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
172475
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);
|
|
172476
172660
|
|
|
172477
|
-
var EXPRESSIONS = /*#__PURE__*/Object.freeze({
|
|
172478
|
-
__proto__: null,
|
|
172479
|
-
ARIA_ATTR: ARIA_ATTR,
|
|
172480
|
-
ATTR_WHITESPACE: ATTR_WHITESPACE,
|
|
172481
|
-
CUSTOM_ELEMENT: CUSTOM_ELEMENT,
|
|
172482
|
-
DATA_ATTR: DATA_ATTR,
|
|
172483
|
-
DOCTYPE_NAME: DOCTYPE_NAME,
|
|
172484
|
-
ERB_EXPR: ERB_EXPR,
|
|
172485
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI,
|
|
172486
|
-
IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
|
|
172487
|
-
MUSTACHE_EXPR: MUSTACHE_EXPR,
|
|
172488
|
-
TMPLIT_EXPR: TMPLIT_EXPR
|
|
172489
|
-
});
|
|
172490
|
-
|
|
172491
|
-
/* eslint-disable @typescript-eslint/indent */
|
|
172492
172661
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
172493
172662
|
const NODE_TYPE = {
|
|
172494
172663
|
element: 1,
|
|
172664
|
+
attribute: 2,
|
|
172495
172665
|
text: 3,
|
|
172666
|
+
cdataSection: 4,
|
|
172667
|
+
entityReference: 5,
|
|
172496
172668
|
// Deprecated
|
|
172497
|
-
|
|
172669
|
+
entityNode: 6,
|
|
172670
|
+
// Deprecated
|
|
172671
|
+
processingInstruction: 7,
|
|
172498
172672
|
comment: 8,
|
|
172499
|
-
document: 9
|
|
172673
|
+
document: 9,
|
|
172674
|
+
documentType: 10,
|
|
172675
|
+
documentFragment: 11,
|
|
172676
|
+
notation: 12 // Deprecated
|
|
172677
|
+
};
|
|
172500
172678
|
const getGlobal = function getGlobal() {
|
|
172501
172679
|
return typeof window === 'undefined' ? null : window;
|
|
172502
172680
|
};
|
|
@@ -172551,10 +172729,25 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
172551
172729
|
uponSanitizeShadowNode: []
|
|
172552
172730
|
};
|
|
172553
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
|
+
};
|
|
172554
172747
|
function createDOMPurify() {
|
|
172555
172748
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
172556
172749
|
const DOMPurify = root => createDOMPurify(root);
|
|
172557
|
-
DOMPurify.version = '3.4.
|
|
172750
|
+
DOMPurify.version = '3.4.11';
|
|
172558
172751
|
DOMPurify.removed = [];
|
|
172559
172752
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
172560
172753
|
// Not running in a browser, provide a factory function
|
|
@@ -172562,28 +172755,29 @@ function createDOMPurify() {
|
|
|
172562
172755
|
DOMPurify.isSupported = false;
|
|
172563
172756
|
return DOMPurify;
|
|
172564
172757
|
}
|
|
172565
|
-
let
|
|
172566
|
-
document
|
|
172567
|
-
} = window;
|
|
172758
|
+
let document = window.document;
|
|
172568
172759
|
const originalDocument = document;
|
|
172569
172760
|
const currentScript = originalDocument.currentScript;
|
|
172570
|
-
|
|
172571
|
-
|
|
172572
|
-
|
|
172573
|
-
|
|
172574
|
-
|
|
172575
|
-
|
|
172576
|
-
NamedNodeMap
|
|
172577
|
-
HTMLFormElement
|
|
172578
|
-
DOMParser,
|
|
172579
|
-
trustedTypes
|
|
172580
|
-
} = 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;
|
|
172581
172771
|
const ElementPrototype = Element.prototype;
|
|
172582
172772
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
172583
172773
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
172584
172774
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
172585
172775
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
172586
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;
|
|
172587
172781
|
// As per issue #47, the web-components registry is inherited by a
|
|
172588
172782
|
// new document created via createHTMLDocument. As per the spec
|
|
172589
172783
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -172598,33 +172792,74 @@ function createDOMPurify() {
|
|
|
172598
172792
|
}
|
|
172599
172793
|
let trustedTypesPolicy;
|
|
172600
172794
|
let emptyHTML = '';
|
|
172601
|
-
|
|
172602
|
-
|
|
172603
|
-
|
|
172604
|
-
|
|
172605
|
-
|
|
172606
|
-
|
|
172607
|
-
|
|
172608
|
-
|
|
172609
|
-
|
|
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;
|
|
172610
172849
|
let hooks = _createHooksMap();
|
|
172611
172850
|
/**
|
|
172612
172851
|
* Expose whether this browser supports running the full DOMPurify.
|
|
172613
172852
|
*/
|
|
172614
172853
|
DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
|
|
172615
|
-
const
|
|
172616
|
-
|
|
172617
|
-
|
|
172618
|
-
|
|
172619
|
-
|
|
172620
|
-
|
|
172621
|
-
|
|
172622
|
-
|
|
172623
|
-
|
|
172624
|
-
} = EXPRESSIONS;
|
|
172625
|
-
let {
|
|
172626
|
-
IS_ALLOWED_URI: IS_ALLOWED_URI$1
|
|
172627
|
-
} = 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;
|
|
172628
172863
|
/**
|
|
172629
172864
|
* We consider the elements and attributes below to be safe. Ideally
|
|
172630
172865
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -172701,6 +172936,13 @@ function createDOMPurify() {
|
|
|
172701
172936
|
let WHOLE_DOCUMENT = false;
|
|
172702
172937
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
172703
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;
|
|
172704
172946
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
172705
172947
|
* document.body. By default, browsers might move them to document.head */
|
|
172706
172948
|
let FORCE_BODY = false;
|
|
@@ -172743,7 +172985,17 @@ function createDOMPurify() {
|
|
|
172743
172985
|
let USE_PROFILES = {};
|
|
172744
172986
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
172745
172987
|
let FORBID_CONTENTS = null;
|
|
172746
|
-
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']);
|
|
172747
172999
|
/* Tags that are safe for data: URIs */
|
|
172748
173000
|
let DATA_URI_TAGS = null;
|
|
172749
173001
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
@@ -172759,8 +173011,10 @@ function createDOMPurify() {
|
|
|
172759
173011
|
/* Allowed XHTML+XML namespaces */
|
|
172760
173012
|
let ALLOWED_NAMESPACES = null;
|
|
172761
173013
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
172762
|
-
|
|
172763
|
-
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);
|
|
172764
173018
|
// Certain elements are allowed in both SVG and HTML
|
|
172765
173019
|
// namespace. We need to specify them explicitly
|
|
172766
173020
|
// so that they don't get erroneously deleted from
|
|
@@ -172802,15 +173056,33 @@ function createDOMPurify() {
|
|
|
172802
173056
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
172803
173057
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
172804
173058
|
/* Set configuration parameters */
|
|
172805
|
-
ALLOWED_TAGS =
|
|
172806
|
-
|
|
172807
|
-
|
|
172808
|
-
|
|
172809
|
-
|
|
172810
|
-
|
|
172811
|
-
|
|
172812
|
-
|
|
172813
|
-
|
|
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;
|
|
172814
173086
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
172815
173087
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
172816
173088
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -172826,20 +173098,22 @@ function createDOMPurify() {
|
|
|
172826
173098
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
172827
173099
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
172828
173100
|
IN_PLACE = cfg.IN_PLACE || false; // Default false
|
|
172829
|
-
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP
|
|
172830
|
-
NAMESPACE = cfg.NAMESPACE
|
|
172831
|
-
MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS
|
|
172832
|
-
HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS
|
|
172833
|
-
|
|
172834
|
-
|
|
172835
|
-
|
|
172836
|
-
|
|
172837
|
-
|
|
172838
|
-
|
|
172839
|
-
|
|
172840
|
-
|
|
172841
|
-
|
|
172842
|
-
|
|
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);
|
|
172843
173117
|
if (SAFE_FOR_TEMPLATES) {
|
|
172844
173118
|
ALLOW_DATA_ATTR = false;
|
|
172845
173119
|
}
|
|
@@ -172875,36 +173149,36 @@ function createDOMPurify() {
|
|
|
172875
173149
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
172876
173150
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
172877
173151
|
/* Merge configuration parameters */
|
|
172878
|
-
if (cfg
|
|
173152
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
172879
173153
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
172880
173154
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
172881
|
-
} else {
|
|
173155
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
172882
173156
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
172883
173157
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
172884
173158
|
}
|
|
172885
173159
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
172886
173160
|
}
|
|
172887
173161
|
}
|
|
172888
|
-
if (cfg
|
|
173162
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
172889
173163
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
172890
173164
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
172891
|
-
} else {
|
|
173165
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
172892
173166
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
172893
173167
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
172894
173168
|
}
|
|
172895
173169
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
172896
173170
|
}
|
|
172897
173171
|
}
|
|
172898
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
173172
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
172899
173173
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
172900
173174
|
}
|
|
172901
|
-
if (cfg.FORBID_CONTENTS) {
|
|
173175
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
172902
173176
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172903
173177
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172904
173178
|
}
|
|
172905
173179
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
172906
173180
|
}
|
|
172907
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
173181
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
172908
173182
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172909
173183
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172910
173184
|
}
|
|
@@ -172923,6 +173197,13 @@ function createDOMPurify() {
|
|
|
172923
173197
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
172924
173198
|
delete FORBID_TAGS.tbody;
|
|
172925
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.
|
|
172926
173207
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
172927
173208
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
172928
173209
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
@@ -172930,18 +173211,45 @@ function createDOMPurify() {
|
|
|
172930
173211
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
172931
173212
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
172932
173213
|
}
|
|
172933
|
-
//
|
|
173214
|
+
// A caller-supplied policy applies to this configuration only.
|
|
173215
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
172934
173216
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
172935
|
-
// Sign local variables required by `sanitize`.
|
|
172936
|
-
|
|
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 = '';
|
|
172937
173237
|
} else {
|
|
172938
|
-
//
|
|
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.)
|
|
172939
173244
|
if (trustedTypesPolicy === undefined) {
|
|
172940
|
-
trustedTypesPolicy =
|
|
173245
|
+
trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
172941
173246
|
}
|
|
172942
|
-
//
|
|
172943
|
-
|
|
172944
|
-
|
|
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('');
|
|
172945
173253
|
}
|
|
172946
173254
|
}
|
|
172947
173255
|
// Prevent further manipulation of configuration.
|
|
@@ -172956,6 +173264,77 @@ function createDOMPurify() {
|
|
|
172956
173264
|
* correctly. */
|
|
172957
173265
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
172958
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
|
+
};
|
|
172959
173338
|
/**
|
|
172960
173339
|
* @param element a DOM element whose namespace is being checked
|
|
172961
173340
|
* @returns Return false if the element has a
|
|
@@ -172978,51 +173357,13 @@ function createDOMPurify() {
|
|
|
172978
173357
|
return false;
|
|
172979
173358
|
}
|
|
172980
173359
|
if (element.namespaceURI === SVG_NAMESPACE) {
|
|
172981
|
-
|
|
172982
|
-
// is via <svg>. If it happens via any other tag, then
|
|
172983
|
-
// it should be killed.
|
|
172984
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
172985
|
-
return tagName === 'svg';
|
|
172986
|
-
}
|
|
172987
|
-
// The only way to switch from MathML to SVG is via`
|
|
172988
|
-
// svg if parent is either <annotation-xml> or MathML
|
|
172989
|
-
// text integration points.
|
|
172990
|
-
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
172991
|
-
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
172992
|
-
}
|
|
172993
|
-
// We only allow elements that are defined in SVG
|
|
172994
|
-
// spec. All others are disallowed in SVG namespace.
|
|
172995
|
-
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
173360
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
172996
173361
|
}
|
|
172997
173362
|
if (element.namespaceURI === MATHML_NAMESPACE) {
|
|
172998
|
-
|
|
172999
|
-
// is via <math>. If it happens via any other tag, then
|
|
173000
|
-
// it should be killed.
|
|
173001
|
-
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
173002
|
-
return tagName === 'math';
|
|
173003
|
-
}
|
|
173004
|
-
// The only way to switch from SVG to MathML is via
|
|
173005
|
-
// <math> and HTML integration points
|
|
173006
|
-
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
173007
|
-
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
173008
|
-
}
|
|
173009
|
-
// We only allow elements that are defined in MathML
|
|
173010
|
-
// spec. All others are disallowed in MathML namespace.
|
|
173011
|
-
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
173363
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
173012
173364
|
}
|
|
173013
173365
|
if (element.namespaceURI === HTML_NAMESPACE) {
|
|
173014
|
-
|
|
173015
|
-
// HTML integration points, and from MathML to HTML
|
|
173016
|
-
// is via MathML text integration points
|
|
173017
|
-
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
173018
|
-
return false;
|
|
173019
|
-
}
|
|
173020
|
-
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
173021
|
-
return false;
|
|
173022
|
-
}
|
|
173023
|
-
// We disallow tags that are specific for MathML
|
|
173024
|
-
// or SVG and should never appear in HTML namespace
|
|
173025
|
-
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
173366
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
173026
173367
|
}
|
|
173027
173368
|
// For XHTML and XML documents that support custom namespaces
|
|
173028
173369
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -173047,7 +173388,74 @@ function createDOMPurify() {
|
|
|
173047
173388
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
173048
173389
|
getParentNode(node).removeChild(node);
|
|
173049
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. */
|
|
173050
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
|
+
}
|
|
173051
173459
|
}
|
|
173052
173460
|
};
|
|
173053
173461
|
/**
|
|
@@ -173082,6 +173490,72 @@ function createDOMPurify() {
|
|
|
173082
173490
|
}
|
|
173083
173491
|
}
|
|
173084
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
|
+
};
|
|
173085
173559
|
/**
|
|
173086
173560
|
* _initDocument
|
|
173087
173561
|
*
|
|
@@ -173103,7 +173577,7 @@ function createDOMPurify() {
|
|
|
173103
173577
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
173104
173578
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
173105
173579
|
}
|
|
173106
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
173580
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
173107
173581
|
/*
|
|
173108
173582
|
* Use the DOMParser API by default, fallback later if needs be
|
|
173109
173583
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -173143,29 +173617,254 @@ function createDOMPurify() {
|
|
|
173143
173617
|
// eslint-disable-next-line no-bitwise
|
|
173144
173618
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
173145
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
|
+
};
|
|
173146
173676
|
/**
|
|
173147
173677
|
* _isClobbered
|
|
173148
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
|
+
*
|
|
173149
173685
|
* @param element element to check for clobbering attacks
|
|
173150
173686
|
* @return true if clobbered, false if safe
|
|
173151
173687
|
*/
|
|
173152
173688
|
const _isClobbered = function _isClobbered(element) {
|
|
173153
|
-
|
|
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
|
+
}
|
|
173154
173747
|
};
|
|
173155
173748
|
/**
|
|
173156
|
-
* 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.
|
|
173157
173755
|
*
|
|
173158
173756
|
* @param value object to check whether it's a DOM node
|
|
173159
|
-
* @return true
|
|
173757
|
+
* @return true if value is a DOM node from any realm
|
|
173160
173758
|
*/
|
|
173161
173759
|
const _isNode = function _isNode(value) {
|
|
173162
|
-
|
|
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
|
+
}
|
|
173163
173768
|
};
|
|
173164
173769
|
function _executeHooks(hooks, currentNode, data) {
|
|
173770
|
+
if (hooks.length === 0) {
|
|
173771
|
+
return;
|
|
173772
|
+
}
|
|
173165
173773
|
arrayForEach(hooks, hook => {
|
|
173166
173774
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
173167
173775
|
});
|
|
173168
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
|
+
};
|
|
173169
173868
|
/**
|
|
173170
173869
|
* _sanitizeElements
|
|
173171
173870
|
*
|
|
@@ -173176,7 +173875,6 @@ function createDOMPurify() {
|
|
|
173176
173875
|
* @return true if node was killed, false if left alive
|
|
173177
173876
|
*/
|
|
173178
173877
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
173179
|
-
let content = null;
|
|
173180
173878
|
/* Execute a hook if present */
|
|
173181
173879
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
173182
173880
|
/* Check if element is clobbered or can clobber */
|
|
@@ -173185,76 +173883,41 @@ function createDOMPurify() {
|
|
|
173185
173883
|
return true;
|
|
173186
173884
|
}
|
|
173187
173885
|
/* Now let's check the element's type and name */
|
|
173188
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
173886
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
173189
173887
|
/* Execute a hook if present */
|
|
173190
173888
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
173191
173889
|
tagName,
|
|
173192
173890
|
allowedTags: ALLOWED_TAGS
|
|
173193
173891
|
});
|
|
173194
|
-
/*
|
|
173195
|
-
if (
|
|
173196
|
-
_forceRemove(currentNode);
|
|
173197
|
-
return true;
|
|
173198
|
-
}
|
|
173199
|
-
/* Remove risky CSS construction leading to mXSS */
|
|
173200
|
-
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
173201
|
-
_forceRemove(currentNode);
|
|
173202
|
-
return true;
|
|
173203
|
-
}
|
|
173204
|
-
/* Remove any occurrence of processing instructions */
|
|
173205
|
-
if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
|
|
173206
|
-
_forceRemove(currentNode);
|
|
173207
|
-
return true;
|
|
173208
|
-
}
|
|
173209
|
-
/* Remove any kind of possibly harmful comments */
|
|
173210
|
-
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)) {
|
|
173211
173894
|
_forceRemove(currentNode);
|
|
173212
173895
|
return true;
|
|
173213
173896
|
}
|
|
173214
173897
|
/* Remove element if anything forbids its presence */
|
|
173215
173898
|
if (FORBID_TAGS[tagName] || !(EXTRA_ELEMENT_HANDLING.tagCheck instanceof Function && EXTRA_ELEMENT_HANDLING.tagCheck(tagName)) && !ALLOWED_TAGS[tagName]) {
|
|
173216
|
-
|
|
173217
|
-
|
|
173218
|
-
|
|
173219
|
-
|
|
173220
|
-
|
|
173221
|
-
|
|
173222
|
-
|
|
173223
|
-
|
|
173224
|
-
|
|
173225
|
-
|
|
173226
|
-
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
173227
|
-
const parentNode = getParentNode(currentNode) || currentNode.parentNode;
|
|
173228
|
-
const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
|
|
173229
|
-
if (childNodes && parentNode) {
|
|
173230
|
-
const childCount = childNodes.length;
|
|
173231
|
-
for (let i = childCount - 1; i >= 0; --i) {
|
|
173232
|
-
const childClone = cloneNode(childNodes[i], true);
|
|
173233
|
-
childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
|
|
173234
|
-
parentNode.insertBefore(childClone, getNextSibling(currentNode));
|
|
173235
|
-
}
|
|
173236
|
-
}
|
|
173237
|
-
}
|
|
173238
|
-
_forceRemove(currentNode);
|
|
173239
|
-
return true;
|
|
173240
|
-
}
|
|
173241
|
-
/* Check whether element has a valid namespace */
|
|
173242
|
-
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)) {
|
|
173243
173909
|
_forceRemove(currentNode);
|
|
173244
173910
|
return true;
|
|
173245
173911
|
}
|
|
173246
173912
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
173247
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
173913
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
173248
173914
|
_forceRemove(currentNode);
|
|
173249
173915
|
return true;
|
|
173250
173916
|
}
|
|
173251
173917
|
/* Sanitize element content to be template-safe */
|
|
173252
173918
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
173253
173919
|
/* Get the element's text content */
|
|
173254
|
-
content = currentNode.textContent;
|
|
173255
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
173256
|
-
content = stringReplace(content, expr, ' ');
|
|
173257
|
-
});
|
|
173920
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
173258
173921
|
if (currentNode.textContent !== content) {
|
|
173259
173922
|
arrayPush(DOMPurify.removed, {
|
|
173260
173923
|
element: currentNode.cloneNode()
|
|
@@ -173284,11 +173947,12 @@ function createDOMPurify() {
|
|
|
173284
173947
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
173285
173948
|
return false;
|
|
173286
173949
|
}
|
|
173950
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
173287
173951
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
173288
173952
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
173289
173953
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
173290
173954
|
We don't need to check the value; it's always URI safe. */
|
|
173291
|
-
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) {
|
|
173292
173956
|
if (
|
|
173293
173957
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
173294
173958
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -173300,11 +173964,15 @@ function createDOMPurify() {
|
|
|
173300
173964
|
return false;
|
|
173301
173965
|
}
|
|
173302
173966
|
/* Check value is safe. First, is attr inert? If so, is safe */
|
|
173303
|
-
} 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) {
|
|
173304
173968
|
return false;
|
|
173305
173969
|
} else ;
|
|
173306
173970
|
return true;
|
|
173307
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']);
|
|
173308
173976
|
/**
|
|
173309
173977
|
* _isBasicCustomElement
|
|
173310
173978
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -173314,7 +173982,64 @@ function createDOMPurify() {
|
|
|
173314
173982
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
173315
173983
|
*/
|
|
173316
173984
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
173317
|
-
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
|
+
}
|
|
173318
174043
|
};
|
|
173319
174044
|
/**
|
|
173320
174045
|
* _sanitizeAttributes
|
|
@@ -173329,9 +174054,7 @@ function createDOMPurify() {
|
|
|
173329
174054
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
173330
174055
|
/* Execute a hook if present */
|
|
173331
174056
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
173332
|
-
const
|
|
173333
|
-
attributes
|
|
173334
|
-
} = currentNode;
|
|
174057
|
+
const attributes = currentNode.attributes;
|
|
173335
174058
|
/* Check if we have attributes; if not we might have a text node */
|
|
173336
174059
|
if (!attributes || _isClobbered(currentNode)) {
|
|
173337
174060
|
return;
|
|
@@ -173344,14 +174067,13 @@ function createDOMPurify() {
|
|
|
173344
174067
|
forceKeepAttr: undefined
|
|
173345
174068
|
};
|
|
173346
174069
|
let l = attributes.length;
|
|
174070
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173347
174071
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
173348
174072
|
while (l--) {
|
|
173349
174073
|
const attr = attributes[l];
|
|
173350
|
-
const
|
|
173351
|
-
|
|
173352
|
-
|
|
173353
|
-
value: attrValue
|
|
173354
|
-
} = attr;
|
|
174074
|
+
const name = attr.name,
|
|
174075
|
+
namespaceURI = attr.namespaceURI,
|
|
174076
|
+
attrValue = attr.value;
|
|
173355
174077
|
const lcName = transformCaseFunc(name);
|
|
173356
174078
|
const initValue = attrValue;
|
|
173357
174079
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -173365,12 +174087,14 @@ function createDOMPurify() {
|
|
|
173365
174087
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
173366
174088
|
* Prefix id and name attributes with `user-content-`
|
|
173367
174089
|
*/
|
|
173368
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
174090
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
173369
174091
|
// Remove the attribute with this value
|
|
173370
174092
|
_removeAttribute(name, currentNode);
|
|
173371
174093
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
173372
174094
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
173373
174095
|
}
|
|
174096
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
174097
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
173374
174098
|
/* Work around a security issue with comments inside attributes */
|
|
173375
174099
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
173376
174100
|
_removeAttribute(name, currentNode);
|
|
@@ -173381,7 +174105,7 @@ function createDOMPurify() {
|
|
|
173381
174105
|
_removeAttribute(name, currentNode);
|
|
173382
174106
|
continue;
|
|
173383
174107
|
}
|
|
173384
|
-
/* Did the hooks
|
|
174108
|
+
/* Did the hooks force-keep the attribute? */
|
|
173385
174109
|
if (hookEvent.forceKeepAttr) {
|
|
173386
174110
|
continue;
|
|
173387
174111
|
}
|
|
@@ -173391,56 +174115,24 @@ function createDOMPurify() {
|
|
|
173391
174115
|
continue;
|
|
173392
174116
|
}
|
|
173393
174117
|
/* Work around a security issue in jQuery 3.0 */
|
|
173394
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
174118
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
173395
174119
|
_removeAttribute(name, currentNode);
|
|
173396
174120
|
continue;
|
|
173397
174121
|
}
|
|
173398
174122
|
/* Sanitize attribute content to be template-safe */
|
|
173399
174123
|
if (SAFE_FOR_TEMPLATES) {
|
|
173400
|
-
|
|
173401
|
-
value = stringReplace(value, expr, ' ');
|
|
173402
|
-
});
|
|
174124
|
+
value = _stripTemplateExpressions(value);
|
|
173403
174125
|
}
|
|
173404
174126
|
/* Is `value` valid for this attribute? */
|
|
173405
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173406
174127
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
173407
174128
|
_removeAttribute(name, currentNode);
|
|
173408
174129
|
continue;
|
|
173409
174130
|
}
|
|
173410
174131
|
/* Handle attributes that require Trusted Types */
|
|
173411
|
-
|
|
173412
|
-
if (namespaceURI) ; else {
|
|
173413
|
-
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
173414
|
-
case 'TrustedHTML':
|
|
173415
|
-
{
|
|
173416
|
-
value = trustedTypesPolicy.createHTML(value);
|
|
173417
|
-
break;
|
|
173418
|
-
}
|
|
173419
|
-
case 'TrustedScriptURL':
|
|
173420
|
-
{
|
|
173421
|
-
value = trustedTypesPolicy.createScriptURL(value);
|
|
173422
|
-
break;
|
|
173423
|
-
}
|
|
173424
|
-
}
|
|
173425
|
-
}
|
|
173426
|
-
}
|
|
174132
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
173427
174133
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
173428
174134
|
if (value !== initValue) {
|
|
173429
|
-
|
|
173430
|
-
if (namespaceURI) {
|
|
173431
|
-
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
173432
|
-
} else {
|
|
173433
|
-
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
173434
|
-
currentNode.setAttribute(name, value);
|
|
173435
|
-
}
|
|
173436
|
-
if (_isClobbered(currentNode)) {
|
|
173437
|
-
_forceRemove(currentNode);
|
|
173438
|
-
} else {
|
|
173439
|
-
arrayPop(DOMPurify.removed);
|
|
173440
|
-
}
|
|
173441
|
-
} catch (_) {
|
|
173442
|
-
_removeAttribute(name, currentNode);
|
|
173443
|
-
}
|
|
174135
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
173444
174136
|
}
|
|
173445
174137
|
}
|
|
173446
174138
|
/* Execute a hook if present */
|
|
@@ -173463,14 +174155,133 @@ function createDOMPurify() {
|
|
|
173463
174155
|
_sanitizeElements(shadowNode);
|
|
173464
174156
|
/* Check attributes next */
|
|
173465
174157
|
_sanitizeAttributes(shadowNode);
|
|
173466
|
-
/* Deep shadow DOM detected
|
|
173467
|
-
|
|
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)) {
|
|
173468
174163
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
173469
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
|
+
}
|
|
173470
174183
|
}
|
|
173471
174184
|
/* Execute a hook if present */
|
|
173472
174185
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
173473
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
|
+
};
|
|
173474
174285
|
// eslint-disable-next-line complexity
|
|
173475
174286
|
DOMPurify.sanitize = function (dirty) {
|
|
173476
174287
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -173487,13 +174298,9 @@ function createDOMPurify() {
|
|
|
173487
174298
|
}
|
|
173488
174299
|
/* Stringify, in case dirty is an object */
|
|
173489
174300
|
if (typeof dirty !== 'string' && !_isNode(dirty)) {
|
|
173490
|
-
|
|
173491
|
-
|
|
173492
|
-
|
|
173493
|
-
throw typeErrorCreate('dirty is not a string, aborting');
|
|
173494
|
-
}
|
|
173495
|
-
} else {
|
|
173496
|
-
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');
|
|
173497
174304
|
}
|
|
173498
174305
|
}
|
|
173499
174306
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -173501,24 +174308,78 @@ function createDOMPurify() {
|
|
|
173501
174308
|
return dirty;
|
|
173502
174309
|
}
|
|
173503
174310
|
/* Assign config vars */
|
|
173504
|
-
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 {
|
|
173505
174319
|
_parseConfig(cfg);
|
|
173506
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
|
+
}
|
|
173507
174336
|
/* Clean up removed elements */
|
|
173508
174337
|
DOMPurify.removed = [];
|
|
173509
|
-
/*
|
|
173510
|
-
|
|
173511
|
-
|
|
173512
|
-
|
|
173513
|
-
|
|
173514
|
-
|
|
173515
|
-
|
|
173516
|
-
|
|
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);
|
|
173517
174354
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
173518
174355
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
173519
174356
|
}
|
|
173520
174357
|
}
|
|
173521
|
-
|
|
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)) {
|
|
173522
174383
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
173523
174384
|
elements being stripped by the parser */
|
|
173524
174385
|
body = _initDocument('<!---->');
|
|
@@ -173532,12 +174393,18 @@ function createDOMPurify() {
|
|
|
173532
174393
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
173533
174394
|
body.appendChild(importedNode);
|
|
173534
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);
|
|
173535
174402
|
} else {
|
|
173536
174403
|
/* Exit directly if we have nothing to do */
|
|
173537
174404
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
173538
174405
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
173539
174406
|
dirty.indexOf('<') === -1) {
|
|
173540
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174407
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
173541
174408
|
}
|
|
173542
174409
|
/* Initialize the document to work on */
|
|
173543
174410
|
body = _initDocument(dirty);
|
|
@@ -173551,31 +174418,59 @@ function createDOMPurify() {
|
|
|
173551
174418
|
_forceRemove(body.firstChild);
|
|
173552
174419
|
}
|
|
173553
174420
|
/* Get node iterator */
|
|
173554
|
-
const nodeIterator = _createNodeIterator(
|
|
173555
|
-
/* Now start iterating over the created document
|
|
173556
|
-
|
|
173557
|
-
|
|
173558
|
-
|
|
173559
|
-
|
|
173560
|
-
|
|
173561
|
-
|
|
173562
|
-
|
|
173563
|
-
|
|
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);
|
|
173564
174448
|
}
|
|
174449
|
+
throw error;
|
|
173565
174450
|
}
|
|
173566
174451
|
/* If we sanitized `dirty` in-place, return it. */
|
|
173567
|
-
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
|
+
}
|
|
173568
174468
|
return dirty;
|
|
173569
174469
|
}
|
|
173570
174470
|
/* Return sanitized string or DOM */
|
|
173571
174471
|
if (RETURN_DOM) {
|
|
173572
174472
|
if (SAFE_FOR_TEMPLATES) {
|
|
173573
|
-
body
|
|
173574
|
-
let html = body.innerHTML;
|
|
173575
|
-
arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
|
|
173576
|
-
html = stringReplace(html, expr, ' ');
|
|
173577
|
-
});
|
|
173578
|
-
body.innerHTML = html;
|
|
174473
|
+
_scrubTemplateExpressions2(body);
|
|
173579
174474
|
}
|
|
173580
174475
|
if (RETURN_DOM_FRAGMENT) {
|
|
173581
174476
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -173605,20 +174500,28 @@ function createDOMPurify() {
|
|
|
173605
174500
|
}
|
|
173606
174501
|
/* Sanitize final string template-safe */
|
|
173607
174502
|
if (SAFE_FOR_TEMPLATES) {
|
|
173608
|
-
|
|
173609
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
173610
|
-
});
|
|
174503
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
173611
174504
|
}
|
|
173612
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174505
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
173613
174506
|
};
|
|
173614
174507
|
DOMPurify.setConfig = function () {
|
|
173615
174508
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
173616
174509
|
_parseConfig(cfg);
|
|
173617
174510
|
SET_CONFIG = true;
|
|
174511
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
174512
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
173618
174513
|
};
|
|
173619
174514
|
DOMPurify.clearConfig = function () {
|
|
173620
174515
|
CONFIG = null;
|
|
173621
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 = '';
|
|
173622
174525
|
};
|
|
173623
174526
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
173624
174527
|
/* Initialize shared config vars if necessary. */
|
|
@@ -173633,9 +174536,19 @@ function createDOMPurify() {
|
|
|
173633
174536
|
if (typeof hookFunction !== 'function') {
|
|
173634
174537
|
return;
|
|
173635
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
|
+
}
|
|
173636
174546
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
173637
174547
|
};
|
|
173638
174548
|
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
174549
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174550
|
+
return undefined;
|
|
174551
|
+
}
|
|
173639
174552
|
if (hookFunction !== undefined) {
|
|
173640
174553
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
173641
174554
|
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
@@ -173643,6 +174556,9 @@ function createDOMPurify() {
|
|
|
173643
174556
|
return arrayPop(hooks[entryPoint]);
|
|
173644
174557
|
};
|
|
173645
174558
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
174559
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174560
|
+
return;
|
|
174561
|
+
}
|
|
173646
174562
|
hooks[entryPoint] = [];
|
|
173647
174563
|
};
|
|
173648
174564
|
DOMPurify.removeAllHooks = function () {
|