@cognigy/webchat 3.46.0 → 3.48.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 +1383 -437
- 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.48.0
|
|
3
|
+
* https://github.com/Cognigy/Webchat/tree/v3.48.0
|
|
4
|
+
* https://github.com/Cognigy/Webchat/tree/v3.48.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,
|
|
@@ -45241,6 +45281,7 @@ function setHasAcceptedTermsInStorage(browserStorage, userId) {
|
|
|
45241
45281
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
45242
45282
|
|
|
45243
45283
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
45284
|
+
/* harmony export */ IH: () => (/* binding */ stripHtmlToInertText),
|
|
45244
45285
|
/* harmony export */ p9: () => (/* binding */ sanitizeHTML)
|
|
45245
45286
|
/* harmony export */ });
|
|
45246
45287
|
/* unused harmony exports allowedHtmlTags, allowedHtmlAttributes */
|
|
@@ -45262,6 +45303,15 @@ const sanitizeHTML = text => {
|
|
|
45262
45303
|
} : config;
|
|
45263
45304
|
return dompurify__WEBPACK_IMPORTED_MODULE_0__/* ["default"] */ .A.sanitize(text, configToUse).toString();
|
|
45264
45305
|
};
|
|
45306
|
+
const stripHtmlToInertText = text => {
|
|
45307
|
+
// 1. strip tags: parse as HTML and keep only the text content
|
|
45308
|
+
const stripped = new DOMParser().parseFromString(text, "text/html").body.textContent || "";
|
|
45309
|
+
// 2. re-escape so <, >, & render as literal characters and can never
|
|
45310
|
+
// be reinterpreted as markup downstream (robust to nested encoding)
|
|
45311
|
+
const escaper = document.createElement("div");
|
|
45312
|
+
escaper.textContent = stripped;
|
|
45313
|
+
return escaper.innerHTML;
|
|
45314
|
+
};
|
|
45265
45315
|
|
|
45266
45316
|
/***/ },
|
|
45267
45317
|
|
|
@@ -46669,7 +46719,7 @@ const createMessageMiddleware = client => store => next => action => {
|
|
|
46669
46719
|
text = (0,_helper_sanitize__WEBPACK_IMPORTED_MODULE_3__/* .sanitizeHTML */ .p9)(text || "");
|
|
46670
46720
|
}
|
|
46671
46721
|
if (store.getState().config.settings.widgetSettings.disableHtmlInput) {
|
|
46672
|
-
text =
|
|
46722
|
+
text = (0,_helper_sanitize__WEBPACK_IMPORTED_MODULE_3__/* .stripHtmlToInertText */ .IH)(text || "");
|
|
46673
46723
|
}
|
|
46674
46724
|
|
|
46675
46725
|
// data is either a non-empty object or null
|
|
@@ -56925,6 +56975,23 @@ a._button_19rd2_1._disabled_19rd2_52:focus {
|
|
|
56925
56975
|
height: 100%;
|
|
56926
56976
|
border-right: 2px solid var(--cc-primary-contrast-color);
|
|
56927
56977
|
}
|
|
56978
|
+
._srOnly_14hnz_1 {
|
|
56979
|
+
position: absolute;
|
|
56980
|
+
width: 1px;
|
|
56981
|
+
height: 1px;
|
|
56982
|
+
padding: 0;
|
|
56983
|
+
margin: -1px;
|
|
56984
|
+
overflow: hidden;
|
|
56985
|
+
clip: rect(0, 0, 0, 0);
|
|
56986
|
+
white-space: nowrap;
|
|
56987
|
+
border: 0;
|
|
56988
|
+
}
|
|
56989
|
+
|
|
56990
|
+
._divider_14hnz_13 {
|
|
56991
|
+
border-bottom-width: 1px;
|
|
56992
|
+
border-bottom-style: solid;
|
|
56993
|
+
border-bottom-color: var(--cc-black-80);
|
|
56994
|
+
}
|
|
56928
56995
|
._buttons_tq8ye_1 {
|
|
56929
56996
|
align-items: flex-start;
|
|
56930
56997
|
display: flex;
|
|
@@ -57597,36 +57664,19 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57597
57664
|
._mainButtonWrapper_zhv80_144 {
|
|
57598
57665
|
padding: 16px;
|
|
57599
57666
|
}
|
|
57600
|
-
.
|
|
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
|
-
._slideItem_1prat_1 {
|
|
57667
|
+
._slideItem_ldua9_1 {
|
|
57618
57668
|
position: relative;
|
|
57619
57669
|
width: 206px;
|
|
57620
57670
|
/* height: 150px; */
|
|
57621
57671
|
overflow: hidden;
|
|
57622
57672
|
}
|
|
57623
57673
|
|
|
57624
|
-
.
|
|
57674
|
+
._slideItem_ldua9_1 ._top_ldua9_8 {
|
|
57625
57675
|
position: relative;
|
|
57626
57676
|
border-radius: var(--cc-bubble-border-radius);
|
|
57627
57677
|
}
|
|
57628
57678
|
|
|
57629
|
-
.
|
|
57679
|
+
._slideItem_ldua9_1 ._bottom_ldua9_13 {
|
|
57630
57680
|
border-bottom-left-radius: var(--cc-bubble-border-radius);
|
|
57631
57681
|
border-bottom-right-radius: var(--cc-bubble-border-radius);
|
|
57632
57682
|
border: 1px solid var(--cc-black-80);
|
|
@@ -57637,7 +57687,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57637
57687
|
padding: 8px;
|
|
57638
57688
|
}
|
|
57639
57689
|
|
|
57640
|
-
.
|
|
57690
|
+
._slideItem_ldua9_1 ._top_ldua9_8 img {
|
|
57641
57691
|
aspect-ratio: 206/150;
|
|
57642
57692
|
object-fit: cover;
|
|
57643
57693
|
object-position: left;
|
|
@@ -57647,7 +57697,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57647
57697
|
outline: none;
|
|
57648
57698
|
}
|
|
57649
57699
|
|
|
57650
|
-
.
|
|
57700
|
+
._slideItem_ldua9_1 ._brokenImage_ldua9_34 {
|
|
57651
57701
|
aspect-ratio: 206/150;
|
|
57652
57702
|
width: 100%;
|
|
57653
57703
|
display: block;
|
|
@@ -57656,14 +57706,18 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57656
57706
|
background-color: var(--cc-black-80);
|
|
57657
57707
|
}
|
|
57658
57708
|
|
|
57659
|
-
.
|
|
57660
|
-
.
|
|
57661
|
-
.
|
|
57709
|
+
._slideItem_ldua9_1 ._hasExtraInfo_ldua9_43,
|
|
57710
|
+
._slideItem_ldua9_1 ._hasExtraInfo_ldua9_43 img,
|
|
57711
|
+
._slideItem_ldua9_1 ._brokenImage_ldua9_34 {
|
|
57662
57712
|
border-bottom-left-radius: 0px;
|
|
57663
57713
|
border-bottom-right-radius: 0px;
|
|
57664
57714
|
}
|
|
57665
57715
|
|
|
57666
|
-
|
|
57716
|
+
/*
|
|
57717
|
+
** Default (legacy) layout: the title overlays the bottom of the image.
|
|
57718
|
+
** Used when \`galleryCardTitleBelowImage\` is not enabled.
|
|
57719
|
+
*/
|
|
57720
|
+
._slideItem_ldua9_1 ._top_ldua9_8 h4 {
|
|
57667
57721
|
position: absolute;
|
|
57668
57722
|
margin: 0px;
|
|
57669
57723
|
margin-inline-start: 8px;
|
|
@@ -57671,12 +57725,17 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57671
57725
|
color: var(--cc-white);
|
|
57672
57726
|
}
|
|
57673
57727
|
|
|
57674
|
-
|
|
57728
|
+
/*
|
|
57729
|
+
** Opt-in layout (galleryCardTitleBelowImage): the title moves into the content
|
|
57730
|
+
** block beneath the image, so it shares the subtitle's flush margins/padding.
|
|
57731
|
+
*/
|
|
57732
|
+
._slideItem_ldua9_1 ._bottom_ldua9_13 p,
|
|
57733
|
+
._slideItem_ldua9_1 ._bottom_ldua9_13 h4 {
|
|
57675
57734
|
padding: 0px;
|
|
57676
57735
|
margin: 0px;
|
|
57677
57736
|
}
|
|
57678
57737
|
|
|
57679
|
-
.
|
|
57738
|
+
._slideItem_ldua9_1 ._bottom_ldua9_13 ._buttonListItem_ldua9_72 {
|
|
57680
57739
|
width: 100%;
|
|
57681
57740
|
}
|
|
57682
57741
|
|
|
@@ -57686,7 +57745,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
57686
57745
|
** The idea is to integrate with modules CSS in order to increase the specificity
|
|
57687
57746
|
** and avoid conflicts on consumer apps
|
|
57688
57747
|
*/
|
|
57689
|
-
article .swiper.
|
|
57748
|
+
article .swiper._wrapper_ldua9_82 {
|
|
57690
57749
|
margin-left: -20px;
|
|
57691
57750
|
margin-right: -20px;
|
|
57692
57751
|
padding-left: 20px;
|
|
@@ -57700,7 +57759,7 @@ article .swiper._wrapper_1prat_73 {
|
|
|
57700
57759
|
z-index: 1;
|
|
57701
57760
|
display: block;
|
|
57702
57761
|
}
|
|
57703
|
-
article .swiper.
|
|
57762
|
+
article .swiper._wrapper_ldua9_82 .swiper-wrapper {
|
|
57704
57763
|
position: relative;
|
|
57705
57764
|
width: 100%;
|
|
57706
57765
|
/* height: 100%; */
|
|
@@ -57710,15 +57769,15 @@ article .swiper._wrapper_1prat_73 .swiper-wrapper {
|
|
|
57710
57769
|
transition-timing-function: initial;
|
|
57711
57770
|
box-sizing: content-box;
|
|
57712
57771
|
}
|
|
57713
|
-
article .swiper.
|
|
57714
|
-
article .swiper.
|
|
57715
|
-
article .swiper.
|
|
57772
|
+
article .swiper._wrapper_ldua9_82 .swiper-android .swiper-slide,
|
|
57773
|
+
article .swiper._wrapper_ldua9_82 .swiper-ios .swiper-slide,
|
|
57774
|
+
article .swiper._wrapper_ldua9_82 .swiper-wrapper {
|
|
57716
57775
|
transform: translate3d(0px, 0, 0);
|
|
57717
57776
|
}
|
|
57718
|
-
article .swiper.
|
|
57777
|
+
article .swiper._wrapper_ldua9_82 .swiper-horizontal {
|
|
57719
57778
|
touch-action: pan-y;
|
|
57720
57779
|
}
|
|
57721
|
-
article .swiper.
|
|
57780
|
+
article .swiper._wrapper_ldua9_82 .swiper-slide {
|
|
57722
57781
|
flex-shrink: 0;
|
|
57723
57782
|
width: 100%;
|
|
57724
57783
|
height: 100%;
|
|
@@ -57726,15 +57785,15 @@ article .swiper._wrapper_1prat_73 .swiper-slide {
|
|
|
57726
57785
|
transition-property: transform;
|
|
57727
57786
|
display: block;
|
|
57728
57787
|
}
|
|
57729
|
-
article .swiper.
|
|
57788
|
+
article .swiper._wrapper_ldua9_82 .swiper-slide-invisible-blank {
|
|
57730
57789
|
visibility: hidden;
|
|
57731
57790
|
}
|
|
57732
57791
|
|
|
57733
57792
|
/*
|
|
57734
57793
|
** SWIPER PAGINATION
|
|
57735
57794
|
*/
|
|
57736
|
-
article .swiper.
|
|
57737
|
-
article .swiper.
|
|
57795
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-prev,
|
|
57796
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-next {
|
|
57738
57797
|
position: absolute;
|
|
57739
57798
|
top: calc(150px / 2 - 8px);
|
|
57740
57799
|
z-index: 10;
|
|
@@ -57747,51 +57806,51 @@ article .swiper._wrapper_1prat_73 .gallery-button-next {
|
|
|
57747
57806
|
display: flex;
|
|
57748
57807
|
align-items: center;
|
|
57749
57808
|
}
|
|
57750
|
-
article .swiper.
|
|
57809
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-prev {
|
|
57751
57810
|
left: 20px;
|
|
57752
57811
|
}
|
|
57753
|
-
article .swiper.
|
|
57812
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-next {
|
|
57754
57813
|
right: 20px;
|
|
57755
57814
|
transform: rotate(180deg);
|
|
57756
57815
|
}
|
|
57757
|
-
article .swiper.
|
|
57816
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-prev:dir(rtl) {
|
|
57758
57817
|
left: unset;
|
|
57759
57818
|
right: 20px;
|
|
57760
57819
|
transform: rotate(180deg);
|
|
57761
57820
|
}
|
|
57762
|
-
article .swiper.
|
|
57821
|
+
article .swiper._wrapper_ldua9_82 .gallery-button-next:dir(rtl) {
|
|
57763
57822
|
right: unset;
|
|
57764
57823
|
left: 20px;
|
|
57765
57824
|
transform: rotate(0deg);
|
|
57766
57825
|
}
|
|
57767
|
-
article .swiper.
|
|
57826
|
+
article .swiper._wrapper_ldua9_82 .swiper-button-disabled {
|
|
57768
57827
|
opacity: 0;
|
|
57769
57828
|
}
|
|
57770
57829
|
|
|
57771
57830
|
/*
|
|
57772
57831
|
** SWIPER NAVIGATION
|
|
57773
57832
|
*/
|
|
57774
|
-
article .swiper.
|
|
57833
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination {
|
|
57775
57834
|
position: absolute;
|
|
57776
57835
|
text-align: center;
|
|
57777
57836
|
transition: 300ms opacity;
|
|
57778
57837
|
transform: translate3d(0, 0, 0);
|
|
57779
57838
|
z-index: 10;
|
|
57780
57839
|
}
|
|
57781
|
-
article .swiper.
|
|
57840
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination.swiper-pagination-hidden {
|
|
57782
57841
|
opacity: 0;
|
|
57783
57842
|
}
|
|
57784
|
-
article .swiper.
|
|
57785
|
-
article .swiper.
|
|
57843
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-disabled > .swiper-pagination,
|
|
57844
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination.swiper-pagination-disabled {
|
|
57786
57845
|
display: none !important;
|
|
57787
57846
|
}
|
|
57788
|
-
article .swiper.
|
|
57789
|
-
article .swiper.
|
|
57847
|
+
article .swiper._wrapper_ldua9_82 .swiper-horizontal > .swiper-pagination-bullets,
|
|
57848
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullets.swiper-pagination-horizontal {
|
|
57790
57849
|
bottom: 4px;
|
|
57791
57850
|
left: 0;
|
|
57792
57851
|
width: 100%;
|
|
57793
57852
|
}
|
|
57794
|
-
article .swiper.
|
|
57853
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet {
|
|
57795
57854
|
width: 6px;
|
|
57796
57855
|
height: 6px;
|
|
57797
57856
|
display: inline-block;
|
|
@@ -57799,16 +57858,16 @@ article .swiper._wrapper_1prat_73 .swiper-pagination-bullet {
|
|
|
57799
57858
|
background: var(--cc-black-50);
|
|
57800
57859
|
opacity: 1;
|
|
57801
57860
|
}
|
|
57802
|
-
article .swiper.
|
|
57861
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet:focus {
|
|
57803
57862
|
background-color: var(--cc-primary-color-focus);
|
|
57804
57863
|
box-shadow: 0 0 0 4px var(--cc-primary-color-opacity-10);
|
|
57805
57864
|
outline: none;
|
|
57806
57865
|
}
|
|
57807
|
-
article .swiper.
|
|
57866
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet:focus-visible {
|
|
57808
57867
|
outline: 2px solid var(--cc-primary-color-focus);
|
|
57809
57868
|
outline-offset: 2px;
|
|
57810
57869
|
}
|
|
57811
|
-
article .swiper.
|
|
57870
|
+
article .swiper._wrapper_ldua9_82 button.swiper-pagination-bullet {
|
|
57812
57871
|
border: none;
|
|
57813
57872
|
margin: 0;
|
|
57814
57873
|
padding: 0;
|
|
@@ -57816,21 +57875,21 @@ article .swiper._wrapper_1prat_73 button.swiper-pagination-bullet {
|
|
|
57816
57875
|
-webkit-appearance: none;
|
|
57817
57876
|
appearance: none;
|
|
57818
57877
|
}
|
|
57819
|
-
article .swiper.
|
|
57878
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-clickable .swiper-pagination-bullet {
|
|
57820
57879
|
cursor: pointer;
|
|
57821
57880
|
}
|
|
57822
|
-
article .swiper.
|
|
57881
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet:only-child {
|
|
57823
57882
|
display: none !important;
|
|
57824
57883
|
}
|
|
57825
|
-
article .swiper.
|
|
57884
|
+
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet-active {
|
|
57826
57885
|
opacity: 1;
|
|
57827
57886
|
background-color: black;
|
|
57828
57887
|
}
|
|
57829
57888
|
article
|
|
57830
|
-
.swiper.
|
|
57889
|
+
.swiper._wrapper_ldua9_82
|
|
57831
57890
|
.swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet,
|
|
57832
57891
|
article
|
|
57833
|
-
.swiper.
|
|
57892
|
+
.swiper._wrapper_ldua9_82
|
|
57834
57893
|
.swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet {
|
|
57835
57894
|
margin: 0 4px;
|
|
57836
57895
|
}
|
|
@@ -58933,13 +58992,17 @@ article ._header_21mid_1 ._avatar-name_21mid_27 {
|
|
|
58933
58992
|
margin: 0;
|
|
58934
58993
|
padding: 0;
|
|
58935
58994
|
}
|
|
58936
|
-
article .
|
|
58995
|
+
article ._avatar_o5ge0_1 {
|
|
58937
58996
|
border-radius: 1000px;
|
|
58938
58997
|
width: 28px;
|
|
58939
58998
|
height: 28px;
|
|
58940
|
-
background-color: var(--cc-primary-color);
|
|
58941
58999
|
box-sizing: border-box;
|
|
58942
59000
|
}
|
|
59001
|
+
|
|
59002
|
+
/* Background only for the bundled default avatars (see Avatar.tsx); custom images stay transparent (AB#90506). */
|
|
59003
|
+
article ._avatar_o5ge0_1[data-default-avatar] {
|
|
59004
|
+
background-color: var(--cc-primary-color);
|
|
59005
|
+
}
|
|
58943
59006
|
:root {
|
|
58944
59007
|
--cc-font-family: var(--webchat-font-family, Figtree);
|
|
58945
59008
|
--cc-action-button-border-radius: var(--webchat-action-button-border-radius, 19px);
|
|
@@ -85274,11 +85337,16 @@ const Text = props2 => {
|
|
|
85274
85337
|
a: ({
|
|
85275
85338
|
node: _node,
|
|
85276
85339
|
...props22
|
|
85277
|
-
}) =>
|
|
85340
|
+
}) => (
|
|
85341
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- react-markdown
|
|
85342
|
+
component override: the link text always arrives as children via the
|
|
85343
|
+
{...props} spread from the markdown AST; the rule cannot see it. */
|
|
85344
|
+
/* @__PURE__ */
|
|
85345
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85278
85346
|
target: "_blank",
|
|
85279
85347
|
rel: "noreferrer",
|
|
85280
85348
|
...props22
|
|
85281
|
-
}),
|
|
85349
|
+
})),
|
|
85282
85350
|
p: ({
|
|
85283
85351
|
node: _node,
|
|
85284
85352
|
children,
|
|
@@ -85928,6 +85996,12 @@ const classes$e = {
|
|
|
85928
85996
|
imageDisabled,
|
|
85929
85997
|
buttonImageContainer
|
|
85930
85998
|
};
|
|
85999
|
+
const srOnly = "_srOnly_14hnz_1";
|
|
86000
|
+
const divider = "_divider_14hnz_13";
|
|
86001
|
+
const mainClasses = {
|
|
86002
|
+
srOnly,
|
|
86003
|
+
divider
|
|
86004
|
+
};
|
|
85931
86005
|
const ActionButton = props2 => {
|
|
85932
86006
|
const {
|
|
85933
86007
|
button: button2,
|
|
@@ -85950,32 +86024,32 @@ const ActionButton = props2 => {
|
|
|
85950
86024
|
const customAllowedHtmlTags = config2?.settings?.widgetSettings?.customAllowedHtmlTags;
|
|
85951
86025
|
const __html = config2?.settings?.layout?.disableHtmlContentSanitization ? buttonLabel : sanitizeHTMLWithConfig(buttonLabel, customAllowedHtmlTags);
|
|
85952
86026
|
const isPhoneNumber = button2.payload && (buttonType === "phone_number" || buttonType === "user_phone_number");
|
|
85953
|
-
const buttonTitle = button2.title || "";
|
|
85954
86027
|
const isWebURL = "type" in button2 && button2.type === "web_url";
|
|
86028
|
+
const sanitizedButtonUrl = isWebURL ? distExports.sanitizeUrl(button2.url) : void 0;
|
|
86029
|
+
const webUrlHref = config2?.settings?.layout?.disableUrlButtonSanitization ? button2.url : sanitizedButtonUrl === "about:blank" ? "about:blank" : button2.url;
|
|
85955
86030
|
const isWebURLButtonTargetBlank = isWebURL && button2.target !== "_self";
|
|
86031
|
+
const opensInNewTab = config2?.settings?.layout?.disableUrlButtonSanitization ? isWebURL && button2.target === "_blank" : isWebURLButtonTargetBlank;
|
|
85956
86032
|
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", {
|
|
86033
|
+
const positionText = total > 1 ? interpolateString(config2?.settings?.customTranslations?.ariaLabels?.actionButtonPositionText ?? "{position} of {total}", {
|
|
86034
|
+
position: position2.toString(),
|
|
86035
|
+
total: total.toString()
|
|
86036
|
+
}) + ": " : null;
|
|
86037
|
+
const PhoneNumberAnchor = props22 => (
|
|
86038
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- render-prop component:
|
|
86039
|
+
the button title always arrives as children via the {...props} spread; the rule
|
|
86040
|
+
cannot see through the indirection. */
|
|
86041
|
+
button2.payload ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85971
86042
|
...props22,
|
|
85972
86043
|
href: `tel:${button2.payload}`
|
|
85973
|
-
}) : null;
|
|
85974
|
-
const Anchor2 = props22 =>
|
|
86044
|
+
}) : null);
|
|
86045
|
+
const Anchor2 = props22 => (
|
|
86046
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- same render-prop pattern
|
|
86047
|
+
as PhoneNumberAnchor: children come in via the {...props} spread. */
|
|
86048
|
+
isWebURL ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
85975
86049
|
...props22,
|
|
85976
|
-
href:
|
|
86050
|
+
href: webUrlHref,
|
|
85977
86051
|
target: button2.target
|
|
85978
|
-
}) : null;
|
|
86052
|
+
}) : null);
|
|
85979
86053
|
const Button2 = props22 => /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
|
|
85980
86054
|
...props22,
|
|
85981
86055
|
disabled: disabled2
|
|
@@ -85993,15 +86067,19 @@ const ActionButton = props2 => {
|
|
|
85993
86067
|
return;
|
|
85994
86068
|
}
|
|
85995
86069
|
if (isWebURL) {
|
|
85996
|
-
|
|
86070
|
+
if (config2?.settings?.layout?.disableUrlButtonSanitization) {
|
|
86071
|
+
if (disabled2) event.preventDefault();
|
|
86072
|
+
return;
|
|
86073
|
+
}
|
|
86074
|
+
event.preventDefault();
|
|
86075
|
+
if (disabled2) return;
|
|
86076
|
+
const url = sanitizedButtonUrl;
|
|
85997
86077
|
if (url === "about:blank") return;
|
|
85998
86078
|
window.open(url, isWebURLButtonTargetBlank ? "_blank" : "_self");
|
|
86079
|
+
return;
|
|
85999
86080
|
}
|
|
86000
86081
|
if (disabled2) return;
|
|
86001
86082
|
event.preventDefault();
|
|
86002
|
-
if (isWebURL) {
|
|
86003
|
-
return;
|
|
86004
|
-
}
|
|
86005
86083
|
if (buttonType === "openXApp") {
|
|
86006
86084
|
openXAppOverlay?.(button2.payload);
|
|
86007
86085
|
return;
|
|
@@ -86029,10 +86107,12 @@ const ActionButton = props2 => {
|
|
|
86029
86107
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(Component, {
|
|
86030
86108
|
onClick: onClick2,
|
|
86031
86109
|
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
86110
|
"aria-disabled": disabled2,
|
|
86034
86111
|
tabIndex: disabled2 ? -1 : 0,
|
|
86035
|
-
children: [
|
|
86112
|
+
children: [positionText && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
86113
|
+
className: mainClasses.srOnly,
|
|
86114
|
+
children: positionText
|
|
86115
|
+
}), !!buttonImage2 && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
|
|
86036
86116
|
className: classes$e.buttonImageContainer,
|
|
86037
86117
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
86038
86118
|
src: buttonImage2,
|
|
@@ -86046,7 +86126,10 @@ const ActionButton = props2 => {
|
|
|
86046
86126
|
__html
|
|
86047
86127
|
},
|
|
86048
86128
|
className: !!buttonImage2 && classes$e.buttonLabelWithImage
|
|
86049
|
-
}), renderIcon()
|
|
86129
|
+
}), renderIcon(), opensInNewTab && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
86130
|
+
className: mainClasses.srOnly,
|
|
86131
|
+
children: `, ${opensInNewTabLabel}`
|
|
86132
|
+
})]
|
|
86050
86133
|
});
|
|
86051
86134
|
};
|
|
86052
86135
|
const buttons$1 = "_buttons_tq8ye_1";
|
|
@@ -92731,7 +92814,12 @@ const Controls = props2 => {
|
|
|
92731
92814
|
})]
|
|
92732
92815
|
})
|
|
92733
92816
|
})]
|
|
92734
|
-
}), altText &&
|
|
92817
|
+
}), altText &&
|
|
92818
|
+
/* eslint-disable-next-line jsx-a11y/anchor-has-content -- intentionally
|
|
92819
|
+
content-less: hidden (display:none, aria-hidden) data-URI download target,
|
|
92820
|
+
activated programmatically by the visible "Download Transcript" button. */
|
|
92821
|
+
/* @__PURE__ */
|
|
92822
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("a", {
|
|
92735
92823
|
ref: downloadTranscriptLinkRef,
|
|
92736
92824
|
href: `data:text/plain;charset=utf-8,${encodeURIComponent(altText)}`,
|
|
92737
92825
|
download: "audio-transcript.txt",
|
|
@@ -92993,12 +93081,6 @@ const classes$9 = {
|
|
|
92993
93081
|
listItemButtonWrapper,
|
|
92994
93082
|
mainButtonWrapper
|
|
92995
93083
|
};
|
|
92996
|
-
const srOnly = "_srOnly_14hnz_1";
|
|
92997
|
-
const divider = "_divider_14hnz_13";
|
|
92998
|
-
const mainClasses = {
|
|
92999
|
-
srOnly,
|
|
93000
|
-
divider
|
|
93001
|
-
};
|
|
93002
93084
|
const ListItem = props2 => {
|
|
93003
93085
|
const {
|
|
93004
93086
|
action,
|
|
@@ -93059,9 +93141,11 @@ const ListItem = props2 => {
|
|
|
93059
93141
|
backgroundImage: getBackgroundImage(image_url)
|
|
93060
93142
|
},
|
|
93061
93143
|
"data-testid": "regular-image",
|
|
93062
|
-
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93144
|
+
children: image_alt_text ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93063
93145
|
role: "img",
|
|
93064
93146
|
"aria-label": image_alt_text
|
|
93147
|
+
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
93148
|
+
"aria-hidden": "true"
|
|
93065
93149
|
})
|
|
93066
93150
|
});
|
|
93067
93151
|
}, [image_alt_text, image_url]);
|
|
@@ -93230,13 +93314,13 @@ const List = () => {
|
|
|
93230
93314
|
})]
|
|
93231
93315
|
});
|
|
93232
93316
|
};
|
|
93233
|
-
const slideItem = "
|
|
93234
|
-
const top = "
|
|
93235
|
-
const bottom = "
|
|
93236
|
-
const brokenImage = "
|
|
93237
|
-
const hasExtraInfo = "
|
|
93238
|
-
const buttonListItem = "
|
|
93239
|
-
const wrapper$1 = "
|
|
93317
|
+
const slideItem = "_slideItem_ldua9_1";
|
|
93318
|
+
const top = "_top_ldua9_8";
|
|
93319
|
+
const bottom = "_bottom_ldua9_13";
|
|
93320
|
+
const brokenImage = "_brokenImage_ldua9_34";
|
|
93321
|
+
const hasExtraInfo = "_hasExtraInfo_ldua9_43";
|
|
93322
|
+
const buttonListItem = "_buttonListItem_ldua9_72";
|
|
93323
|
+
const wrapper$1 = "_wrapper_ldua9_82";
|
|
93240
93324
|
const classes$8 = {
|
|
93241
93325
|
slideItem,
|
|
93242
93326
|
top,
|
|
@@ -99031,12 +99115,15 @@ const GalleryItem = props2 => {
|
|
|
99031
99115
|
"data-message-id": dataMessageId
|
|
99032
99116
|
} = useMessageContext();
|
|
99033
99117
|
const hasExtraInfo2 = subtitle || buttons2 && buttons2?.length > 0;
|
|
99118
|
+
const titleBelowImage = !!config2?.settings?.layout?.galleryCardTitleBelowImage;
|
|
99034
99119
|
const [isImageBroken, setImageBroken] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
|
|
99035
99120
|
const {
|
|
99036
99121
|
processHTML
|
|
99037
99122
|
} = useSanitize();
|
|
99038
|
-
const titleHtml = processHTML(title);
|
|
99039
|
-
const subtitleHtml = processHTML(subtitle);
|
|
99123
|
+
const titleHtml = processHTML(title || "");
|
|
99124
|
+
const subtitleHtml = processHTML(subtitle || "");
|
|
99125
|
+
const hasTitle = !!titleHtml;
|
|
99126
|
+
const showContentBlock = titleBelowImage && hasTitle || hasExtraInfo2;
|
|
99040
99127
|
const titleId = useRandomId("webchatCarouselTemplateTitle");
|
|
99041
99128
|
const subtitleId = useRandomId("webchatCarouselTemplateSubtitle");
|
|
99042
99129
|
const shouldBeDisabled = messageParams?.isConversationEnded;
|
|
@@ -99053,19 +99140,20 @@ const GalleryItem = props2 => {
|
|
|
99053
99140
|
handleClick();
|
|
99054
99141
|
}
|
|
99055
99142
|
};
|
|
99143
|
+
const titleElement = /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
99144
|
+
variant: "body-semibold",
|
|
99145
|
+
component: "h4",
|
|
99146
|
+
dangerouslySetInnerHTML: {
|
|
99147
|
+
__html: titleHtml
|
|
99148
|
+
},
|
|
99149
|
+
className: "webchat-carousel-template-title",
|
|
99150
|
+
id: titleId
|
|
99151
|
+
});
|
|
99056
99152
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
99057
99153
|
className: classnames$1("webchat-carousel-template-frame", classes$8.slideItem),
|
|
99058
99154
|
children: [/* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
99059
|
-
className: classnames$1(classes$8.top,
|
|
99060
|
-
children: [/* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(
|
|
99061
|
-
variant: "body-semibold",
|
|
99062
|
-
component: "h4",
|
|
99063
|
-
dangerouslySetInnerHTML: {
|
|
99064
|
-
__html: titleHtml
|
|
99065
|
-
},
|
|
99066
|
-
className: "webchat-carousel-template-title",
|
|
99067
|
-
id: titleId
|
|
99068
|
-
}), isImageBroken ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
99155
|
+
className: classnames$1(classes$8.top, showContentBlock && classes$8.hasExtraInfo),
|
|
99156
|
+
children: [!titleBelowImage && hasTitle && titleElement, isImageBroken ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
99069
99157
|
className: classes$8.brokenImage
|
|
99070
99158
|
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
99071
99159
|
src: image_url,
|
|
@@ -99073,16 +99161,16 @@ const GalleryItem = props2 => {
|
|
|
99073
99161
|
className: classes$8.slideImage,
|
|
99074
99162
|
onError: () => setImageBroken(true)
|
|
99075
99163
|
})]
|
|
99076
|
-
}),
|
|
99164
|
+
}), showContentBlock && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
99077
99165
|
className: classnames$1("webchat-carousel-template-content", classes$8.bottom),
|
|
99078
99166
|
onClick: handleClick,
|
|
99079
99167
|
onKeyDown: handleKeyDown,
|
|
99080
99168
|
role: default_action?.url ? "link" : void 0,
|
|
99081
99169
|
id: contentId,
|
|
99082
99170
|
"aria-describedby": default_action?.url && subtitle ? subtitleId : void 0,
|
|
99083
|
-
"aria-labelledby": default_action?.url &&
|
|
99171
|
+
"aria-labelledby": default_action?.url && hasTitle ? titleId : void 0,
|
|
99084
99172
|
"aria-label": default_action?.url ? `${titleHtml}. ${opensInNewTab}` : void 0,
|
|
99085
|
-
children: [subtitle && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
99173
|
+
children: [titleBelowImage && hasTitle && titleElement, subtitle && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
99086
99174
|
variant: "body-regular",
|
|
99087
99175
|
dangerouslySetInnerHTML: {
|
|
99088
99176
|
__html: subtitleHtml
|
|
@@ -99097,7 +99185,7 @@ const GalleryItem = props2 => {
|
|
|
99097
99185
|
action: shouldBeDisabled ? void 0 : action,
|
|
99098
99186
|
config: config2,
|
|
99099
99187
|
onEmitAnalytics,
|
|
99100
|
-
templateTextId:
|
|
99188
|
+
templateTextId: hasTitle ? titleId : void 0
|
|
99101
99189
|
})]
|
|
99102
99190
|
})]
|
|
99103
99191
|
});
|
|
@@ -118672,7 +118760,12 @@ const DatePicker = () => {
|
|
|
118672
118760
|
"aria-controls": `webchat-plugin-date-picker-${message2.id}`,
|
|
118673
118761
|
"aria-haspopup": "dialog",
|
|
118674
118762
|
children: openText
|
|
118675
|
-
}), showPicker &&
|
|
118763
|
+
}), showPicker &&
|
|
118764
|
+
/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions -- standard
|
|
118765
|
+
ARIA modal pattern: the keydown listener on the role="dialog" container implements
|
|
118766
|
+
the dialog's focus trap and Escape handling (APG dialog-modal), not a click target. */
|
|
118767
|
+
/* @__PURE__ */
|
|
118768
|
+
(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
118676
118769
|
ref: datePickerRef,
|
|
118677
118770
|
id: `webchat-plugin-date-picker-${message2.id}`,
|
|
118678
118771
|
className: classnames$1(classes$6.wrapper, "webchat-plugin-date-picker"),
|
|
@@ -134729,7 +134822,7 @@ const classes$3 = {
|
|
|
134729
134822
|
timestamp,
|
|
134730
134823
|
"avatar-name": "_avatar-name_21mid_27"
|
|
134731
134824
|
};
|
|
134732
|
-
const avatar = "
|
|
134825
|
+
const avatar = "_avatar_o5ge0_1";
|
|
134733
134826
|
const classes$2 = {
|
|
134734
134827
|
avatar
|
|
134735
134828
|
};
|
|
@@ -134758,11 +134851,13 @@ const Avatar = props2 => {
|
|
|
134758
134851
|
setAvatarUrl(botAvatar);
|
|
134759
134852
|
}
|
|
134760
134853
|
}, [message2.avatarUrl, message2?.source, overrides.agentAvatarOverrideUrlOnce, overrides.botAvatarOverrideUrlOnce]);
|
|
134854
|
+
const isDefaultAvatar = avatarUrl === botAvatar || avatarUrl === placeholderAvatar;
|
|
134761
134855
|
const classNames = classnames$1(classes$2.avatar, props2.className, "webchat-avatar", message2?.source);
|
|
134762
134856
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
134763
134857
|
alt: "",
|
|
134764
134858
|
className: classNames,
|
|
134765
134859
|
src: avatarUrl,
|
|
134860
|
+
"data-default-avatar": isDefaultAvatar ? "" : void 0,
|
|
134766
134861
|
onError: () => setAvatarUrl(placeholderAvatar),
|
|
134767
134862
|
"data-testid": `${message2?.source || "unknown source"}-avatar`
|
|
134768
134863
|
});
|
|
@@ -172256,24 +172351,65 @@ function isUrl(fileUrlOrPath) {
|
|
|
172256
172351
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
172257
172352
|
/* harmony export */ A: () => (/* binding */ purify)
|
|
172258
172353
|
/* harmony export */ });
|
|
172259
|
-
/*! @license DOMPurify 3.4.
|
|
172354
|
+
/*! @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
172355
|
|
|
172261
|
-
|
|
172262
|
-
|
|
172263
|
-
|
|
172264
|
-
|
|
172265
|
-
|
|
172266
|
-
|
|
172267
|
-
|
|
172268
|
-
|
|
172269
|
-
|
|
172270
|
-
|
|
172271
|
-
|
|
172272
|
-
|
|
172273
|
-
|
|
172274
|
-
|
|
172275
|
-
|
|
172276
|
-
|
|
172356
|
+
function _arrayLikeToArray(r, a) {
|
|
172357
|
+
(null == a || a > r.length) && (a = r.length);
|
|
172358
|
+
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
|
|
172359
|
+
return n;
|
|
172360
|
+
}
|
|
172361
|
+
function _arrayWithHoles(r) {
|
|
172362
|
+
if (Array.isArray(r)) return r;
|
|
172363
|
+
}
|
|
172364
|
+
function _iterableToArrayLimit(r, l) {
|
|
172365
|
+
var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
|
|
172366
|
+
if (null != t) {
|
|
172367
|
+
var e,
|
|
172368
|
+
n,
|
|
172369
|
+
i,
|
|
172370
|
+
u,
|
|
172371
|
+
a = [],
|
|
172372
|
+
f = true,
|
|
172373
|
+
o = false;
|
|
172374
|
+
try {
|
|
172375
|
+
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);
|
|
172376
|
+
} catch (r) {
|
|
172377
|
+
o = true, n = r;
|
|
172378
|
+
} finally {
|
|
172379
|
+
try {
|
|
172380
|
+
if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
|
|
172381
|
+
} finally {
|
|
172382
|
+
if (o) throw n;
|
|
172383
|
+
}
|
|
172384
|
+
}
|
|
172385
|
+
return a;
|
|
172386
|
+
}
|
|
172387
|
+
}
|
|
172388
|
+
function _nonIterableRest() {
|
|
172389
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
172390
|
+
}
|
|
172391
|
+
function _slicedToArray(r, e) {
|
|
172392
|
+
return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest();
|
|
172393
|
+
}
|
|
172394
|
+
function _unsupportedIterableToArray(r, a) {
|
|
172395
|
+
if (r) {
|
|
172396
|
+
if ("string" == typeof r) return _arrayLikeToArray(r, a);
|
|
172397
|
+
var t = {}.toString.call(r).slice(8, -1);
|
|
172398
|
+
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;
|
|
172399
|
+
}
|
|
172400
|
+
}
|
|
172401
|
+
|
|
172402
|
+
const entries = Object.entries,
|
|
172403
|
+
setPrototypeOf = Object.setPrototypeOf,
|
|
172404
|
+
isFrozen = Object.isFrozen,
|
|
172405
|
+
getPrototypeOf = Object.getPrototypeOf,
|
|
172406
|
+
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
|
|
172407
|
+
let freeze = Object.freeze,
|
|
172408
|
+
seal = Object.seal,
|
|
172409
|
+
create = Object.create; // eslint-disable-line import/no-mutable-exports
|
|
172410
|
+
let _ref = typeof Reflect !== 'undefined' && Reflect,
|
|
172411
|
+
apply = _ref.apply,
|
|
172412
|
+
construct = _ref.construct;
|
|
172277
172413
|
if (!freeze) {
|
|
172278
172414
|
freeze = function freeze(x) {
|
|
172279
172415
|
return x;
|
|
@@ -172305,13 +172441,19 @@ const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
|
|
|
172305
172441
|
const arrayPop = unapply(Array.prototype.pop);
|
|
172306
172442
|
const arrayPush = unapply(Array.prototype.push);
|
|
172307
172443
|
const arraySplice = unapply(Array.prototype.splice);
|
|
172444
|
+
const arrayIsArray = Array.isArray;
|
|
172308
172445
|
const stringToLowerCase = unapply(String.prototype.toLowerCase);
|
|
172309
172446
|
const stringToString = unapply(String.prototype.toString);
|
|
172310
172447
|
const stringMatch = unapply(String.prototype.match);
|
|
172311
172448
|
const stringReplace = unapply(String.prototype.replace);
|
|
172312
172449
|
const stringIndexOf = unapply(String.prototype.indexOf);
|
|
172313
172450
|
const stringTrim = unapply(String.prototype.trim);
|
|
172451
|
+
const numberToString = unapply(Number.prototype.toString);
|
|
172452
|
+
const booleanToString = unapply(Boolean.prototype.toString);
|
|
172453
|
+
const bigintToString = typeof BigInt === 'undefined' ? null : unapply(BigInt.prototype.toString);
|
|
172454
|
+
const symbolToString = typeof Symbol === 'undefined' ? null : unapply(Symbol.prototype.toString);
|
|
172314
172455
|
const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
|
|
172456
|
+
const objectToString = unapply(Object.prototype.toString);
|
|
172315
172457
|
const regExpTest = unapply(RegExp.prototype.test);
|
|
172316
172458
|
const typeErrorCreate = unconstruct(TypeError);
|
|
172317
172459
|
/**
|
|
@@ -172361,6 +172503,9 @@ function addToSet(set, array) {
|
|
|
172361
172503
|
// Prevent prototype setters from intercepting set as a this value.
|
|
172362
172504
|
setPrototypeOf(set, null);
|
|
172363
172505
|
}
|
|
172506
|
+
if (!arrayIsArray(array)) {
|
|
172507
|
+
return set;
|
|
172508
|
+
}
|
|
172364
172509
|
let l = array.length;
|
|
172365
172510
|
while (l--) {
|
|
172366
172511
|
let element = array[l];
|
|
@@ -172401,10 +172546,13 @@ function cleanArray(array) {
|
|
|
172401
172546
|
*/
|
|
172402
172547
|
function clone(object) {
|
|
172403
172548
|
const newObject = create(null);
|
|
172404
|
-
for (const
|
|
172549
|
+
for (const _ref2 of entries(object)) {
|
|
172550
|
+
var _ref3 = _slicedToArray(_ref2, 2);
|
|
172551
|
+
const property = _ref3[0];
|
|
172552
|
+
const value = _ref3[1];
|
|
172405
172553
|
const isPropertyExist = objectHasOwnProperty(object, property);
|
|
172406
172554
|
if (isPropertyExist) {
|
|
172407
|
-
if (
|
|
172555
|
+
if (arrayIsArray(value)) {
|
|
172408
172556
|
newObject[property] = cleanArray(value);
|
|
172409
172557
|
} else if (value && typeof value === 'object' && value.constructor === Object) {
|
|
172410
172558
|
newObject[property] = clone(value);
|
|
@@ -172415,6 +172563,58 @@ function clone(object) {
|
|
|
172415
172563
|
}
|
|
172416
172564
|
return newObject;
|
|
172417
172565
|
}
|
|
172566
|
+
/**
|
|
172567
|
+
* Convert non-node values into strings without depending on direct property access.
|
|
172568
|
+
*
|
|
172569
|
+
* @param value - The value to stringify.
|
|
172570
|
+
* @returns A string representation of the provided value.
|
|
172571
|
+
*/
|
|
172572
|
+
function stringifyValue(value) {
|
|
172573
|
+
switch (typeof value) {
|
|
172574
|
+
case 'string':
|
|
172575
|
+
{
|
|
172576
|
+
return value;
|
|
172577
|
+
}
|
|
172578
|
+
case 'number':
|
|
172579
|
+
{
|
|
172580
|
+
return numberToString(value);
|
|
172581
|
+
}
|
|
172582
|
+
case 'boolean':
|
|
172583
|
+
{
|
|
172584
|
+
return booleanToString(value);
|
|
172585
|
+
}
|
|
172586
|
+
case 'bigint':
|
|
172587
|
+
{
|
|
172588
|
+
return bigintToString ? bigintToString(value) : '0';
|
|
172589
|
+
}
|
|
172590
|
+
case 'symbol':
|
|
172591
|
+
{
|
|
172592
|
+
return symbolToString ? symbolToString(value) : 'Symbol()';
|
|
172593
|
+
}
|
|
172594
|
+
case 'undefined':
|
|
172595
|
+
{
|
|
172596
|
+
return objectToString(value);
|
|
172597
|
+
}
|
|
172598
|
+
case 'function':
|
|
172599
|
+
case 'object':
|
|
172600
|
+
{
|
|
172601
|
+
if (value === null) {
|
|
172602
|
+
return objectToString(value);
|
|
172603
|
+
}
|
|
172604
|
+
const valueAsRecord = value;
|
|
172605
|
+
const valueToString = lookupGetter(valueAsRecord, 'toString');
|
|
172606
|
+
if (typeof valueToString === 'function') {
|
|
172607
|
+
const stringified = valueToString(valueAsRecord);
|
|
172608
|
+
return typeof stringified === 'string' ? stringified : objectToString(stringified);
|
|
172609
|
+
}
|
|
172610
|
+
return objectToString(value);
|
|
172611
|
+
}
|
|
172612
|
+
default:
|
|
172613
|
+
{
|
|
172614
|
+
return objectToString(value);
|
|
172615
|
+
}
|
|
172616
|
+
}
|
|
172617
|
+
}
|
|
172418
172618
|
/**
|
|
172419
172619
|
* This method automatically checks if the prop is function or getter and behaves accordingly.
|
|
172420
172620
|
*
|
|
@@ -172440,6 +172640,14 @@ function lookupGetter(object, prop) {
|
|
|
172440
172640
|
}
|
|
172441
172641
|
return fallbackValue;
|
|
172442
172642
|
}
|
|
172643
|
+
function isRegex(value) {
|
|
172644
|
+
try {
|
|
172645
|
+
regExpTest(value, '');
|
|
172646
|
+
return true;
|
|
172647
|
+
} catch (_unused) {
|
|
172648
|
+
return false;
|
|
172649
|
+
}
|
|
172650
|
+
}
|
|
172443
172651
|
|
|
172444
172652
|
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
172653
|
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 +172663,14 @@ const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mgly
|
|
|
172455
172663
|
const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
|
|
172456
172664
|
const text = freeze(['#text']);
|
|
172457
172665
|
|
|
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'
|
|
172666
|
+
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
172667
|
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
172668
|
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
172669
|
const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
|
|
172462
172670
|
|
|
172463
|
-
|
|
172464
|
-
const
|
|
172465
|
-
const
|
|
172466
|
-
const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
|
|
172671
|
+
const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g);
|
|
172672
|
+
const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g);
|
|
172673
|
+
const TMPLIT_EXPR = seal(/\${[\w\W]*/g);
|
|
172467
172674
|
const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
|
|
172468
172675
|
const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
|
|
172469
172676
|
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 +172680,31 @@ const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205
|
|
|
172473
172680
|
);
|
|
172474
172681
|
const DOCTYPE_NAME = seal(/^html$/i);
|
|
172475
172682
|
const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
|
|
172683
|
+
// Markup-significant character probes used by _sanitizeElements.
|
|
172684
|
+
// Shared module-level instances are safe despite the sticky /g flags:
|
|
172685
|
+
// unapply() resets lastIndex for RegExp receivers before every call.
|
|
172686
|
+
const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g);
|
|
172687
|
+
const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g);
|
|
172688
|
+
const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i);
|
|
172689
|
+
const SELF_CLOSING_TAG = seal(/\/>/i);
|
|
172476
172690
|
|
|
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
172691
|
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
|
|
172493
172692
|
const NODE_TYPE = {
|
|
172494
172693
|
element: 1,
|
|
172694
|
+
attribute: 2,
|
|
172495
172695
|
text: 3,
|
|
172696
|
+
cdataSection: 4,
|
|
172697
|
+
entityReference: 5,
|
|
172496
172698
|
// Deprecated
|
|
172497
|
-
|
|
172699
|
+
entityNode: 6,
|
|
172700
|
+
// Deprecated
|
|
172701
|
+
processingInstruction: 7,
|
|
172498
172702
|
comment: 8,
|
|
172499
|
-
document: 9
|
|
172703
|
+
document: 9,
|
|
172704
|
+
documentType: 10,
|
|
172705
|
+
documentFragment: 11,
|
|
172706
|
+
notation: 12 // Deprecated
|
|
172707
|
+
};
|
|
172500
172708
|
const getGlobal = function getGlobal() {
|
|
172501
172709
|
return typeof window === 'undefined' ? null : window;
|
|
172502
172710
|
};
|
|
@@ -172551,10 +172759,25 @@ const _createHooksMap = function _createHooksMap() {
|
|
|
172551
172759
|
uponSanitizeShadowNode: []
|
|
172552
172760
|
};
|
|
172553
172761
|
};
|
|
172762
|
+
/**
|
|
172763
|
+
* Resolve a set-valued configuration option: a fresh set built from
|
|
172764
|
+
* cfg[key] when it is an own array property (seeded with a clone of
|
|
172765
|
+
* options.base when given, case-normalized via options.transform),
|
|
172766
|
+
* the fallback set otherwise.
|
|
172767
|
+
*
|
|
172768
|
+
* @param cfg the cloned, prototype-free configuration object
|
|
172769
|
+
* @param key the configuration property to read
|
|
172770
|
+
* @param fallback the set to use when the option is absent or not an array
|
|
172771
|
+
* @param options transform and optional base set to merge into
|
|
172772
|
+
* @returns the resolved set
|
|
172773
|
+
*/
|
|
172774
|
+
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
|
|
172775
|
+
return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback;
|
|
172776
|
+
};
|
|
172554
172777
|
function createDOMPurify() {
|
|
172555
172778
|
let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
|
|
172556
172779
|
const DOMPurify = root => createDOMPurify(root);
|
|
172557
|
-
DOMPurify.version = '3.4.
|
|
172780
|
+
DOMPurify.version = '3.4.11';
|
|
172558
172781
|
DOMPurify.removed = [];
|
|
172559
172782
|
if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
|
|
172560
172783
|
// Not running in a browser, provide a factory function
|
|
@@ -172562,28 +172785,29 @@ function createDOMPurify() {
|
|
|
172562
172785
|
DOMPurify.isSupported = false;
|
|
172563
172786
|
return DOMPurify;
|
|
172564
172787
|
}
|
|
172565
|
-
let
|
|
172566
|
-
document
|
|
172567
|
-
} = window;
|
|
172788
|
+
let document = window.document;
|
|
172568
172789
|
const originalDocument = document;
|
|
172569
172790
|
const currentScript = originalDocument.currentScript;
|
|
172570
|
-
|
|
172571
|
-
|
|
172572
|
-
|
|
172573
|
-
|
|
172574
|
-
|
|
172575
|
-
|
|
172576
|
-
NamedNodeMap
|
|
172577
|
-
HTMLFormElement
|
|
172578
|
-
DOMParser,
|
|
172579
|
-
trustedTypes
|
|
172580
|
-
} = window;
|
|
172791
|
+
window.DocumentFragment;
|
|
172792
|
+
const HTMLTemplateElement = window.HTMLTemplateElement,
|
|
172793
|
+
Node = window.Node,
|
|
172794
|
+
Element = window.Element,
|
|
172795
|
+
NodeFilter = window.NodeFilter,
|
|
172796
|
+
_window$NamedNodeMap = window.NamedNodeMap;
|
|
172797
|
+
_window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap;
|
|
172798
|
+
window.HTMLFormElement;
|
|
172799
|
+
const DOMParser = window.DOMParser,
|
|
172800
|
+
trustedTypes = window.trustedTypes;
|
|
172581
172801
|
const ElementPrototype = Element.prototype;
|
|
172582
172802
|
const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
|
|
172583
172803
|
const remove = lookupGetter(ElementPrototype, 'remove');
|
|
172584
172804
|
const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
|
|
172585
172805
|
const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
|
|
172586
172806
|
const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
|
|
172807
|
+
const getShadowRoot = lookupGetter(ElementPrototype, 'shadowRoot');
|
|
172808
|
+
const getAttributes = lookupGetter(ElementPrototype, 'attributes');
|
|
172809
|
+
const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeType') : null;
|
|
172810
|
+
const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, 'nodeName') : null;
|
|
172587
172811
|
// As per issue #47, the web-components registry is inherited by a
|
|
172588
172812
|
// new document created via createHTMLDocument. As per the spec
|
|
172589
172813
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
|
|
@@ -172598,33 +172822,74 @@ function createDOMPurify() {
|
|
|
172598
172822
|
}
|
|
172599
172823
|
let trustedTypesPolicy;
|
|
172600
172824
|
let emptyHTML = '';
|
|
172601
|
-
|
|
172602
|
-
|
|
172603
|
-
|
|
172604
|
-
|
|
172605
|
-
|
|
172606
|
-
|
|
172607
|
-
|
|
172608
|
-
|
|
172609
|
-
|
|
172825
|
+
// The instance's own internal Trusted Types policy. Unlike a caller-supplied
|
|
172826
|
+
// `TRUSTED_TYPES_POLICY`, this is created at most once — Trusted Types throws
|
|
172827
|
+
// on duplicate policy names — and is the only policy allowed to persist
|
|
172828
|
+
// across configurations and survive `clearConfig()`.
|
|
172829
|
+
let defaultTrustedTypesPolicy;
|
|
172830
|
+
let defaultTrustedTypesPolicyResolved = false;
|
|
172831
|
+
// Tracks whether we are already inside a call to the configured Trusted Types
|
|
172832
|
+
// policy (`createHTML` or `createScriptURL`). If a supplied policy callback
|
|
172833
|
+
// itself calls `DOMPurify.sanitize` (the cause of #1422), `sanitize` would
|
|
172834
|
+
// re-enter the policy and recurse until the stack overflows. We detect that
|
|
172835
|
+
// re-entry and throw a clear, actionable error instead. The guard is shared
|
|
172836
|
+
// across both callbacks, because either one re-entering `sanitize` triggers
|
|
172837
|
+
// the same unbounded recursion.
|
|
172838
|
+
let IN_TRUSTED_TYPES_POLICY = 0;
|
|
172839
|
+
const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() {
|
|
172840
|
+
if (IN_TRUSTED_TYPES_POLICY > 0) {
|
|
172841
|
+
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.');
|
|
172842
|
+
}
|
|
172843
|
+
};
|
|
172844
|
+
const _createTrustedHTML = function _createTrustedHTML(html) {
|
|
172845
|
+
_assertNotInTrustedTypesPolicy();
|
|
172846
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
172847
|
+
try {
|
|
172848
|
+
return trustedTypesPolicy.createHTML(html);
|
|
172849
|
+
} finally {
|
|
172850
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
172851
|
+
}
|
|
172852
|
+
};
|
|
172853
|
+
const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) {
|
|
172854
|
+
_assertNotInTrustedTypesPolicy();
|
|
172855
|
+
IN_TRUSTED_TYPES_POLICY++;
|
|
172856
|
+
try {
|
|
172857
|
+
return trustedTypesPolicy.createScriptURL(scriptUrl);
|
|
172858
|
+
} finally {
|
|
172859
|
+
IN_TRUSTED_TYPES_POLICY--;
|
|
172860
|
+
}
|
|
172861
|
+
};
|
|
172862
|
+
// Lazily resolve (and cache) the instance's internal default policy.
|
|
172863
|
+
// Resolution is attempted at most once: a successful `createPolicy` cannot be
|
|
172864
|
+
// repeated (Trusted Types throws on duplicate names), and a failed or
|
|
172865
|
+
// unsupported attempt must not be retried on every parse.
|
|
172866
|
+
const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() {
|
|
172867
|
+
if (!defaultTrustedTypesPolicyResolved) {
|
|
172868
|
+
defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
|
|
172869
|
+
defaultTrustedTypesPolicyResolved = true;
|
|
172870
|
+
}
|
|
172871
|
+
return defaultTrustedTypesPolicy;
|
|
172872
|
+
};
|
|
172873
|
+
const _document = document,
|
|
172874
|
+
implementation = _document.implementation,
|
|
172875
|
+
createNodeIterator = _document.createNodeIterator,
|
|
172876
|
+
createDocumentFragment = _document.createDocumentFragment,
|
|
172877
|
+
getElementsByTagName = _document.getElementsByTagName;
|
|
172878
|
+
const importNode = originalDocument.importNode;
|
|
172610
172879
|
let hooks = _createHooksMap();
|
|
172611
172880
|
/**
|
|
172612
172881
|
* Expose whether this browser supports running the full DOMPurify.
|
|
172613
172882
|
*/
|
|
172614
172883
|
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;
|
|
172884
|
+
const MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
|
|
172885
|
+
ERB_EXPR$1 = ERB_EXPR,
|
|
172886
|
+
TMPLIT_EXPR$1 = TMPLIT_EXPR,
|
|
172887
|
+
DATA_ATTR$1 = DATA_ATTR,
|
|
172888
|
+
ARIA_ATTR$1 = ARIA_ATTR,
|
|
172889
|
+
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
|
|
172890
|
+
ATTR_WHITESPACE$1 = ATTR_WHITESPACE,
|
|
172891
|
+
CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT;
|
|
172892
|
+
let IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
|
|
172628
172893
|
/**
|
|
172629
172894
|
* We consider the elements and attributes below to be safe. Ideally
|
|
172630
172895
|
* don't add any new ones but feel free to remove unwanted ones.
|
|
@@ -172701,6 +172966,13 @@ function createDOMPurify() {
|
|
|
172701
172966
|
let WHOLE_DOCUMENT = false;
|
|
172702
172967
|
/* Track whether config is already set on this instance of DOMPurify. */
|
|
172703
172968
|
let SET_CONFIG = false;
|
|
172969
|
+
/* Pristine allowlist bindings captured at setConfig() time. On the
|
|
172970
|
+
* persistent-config path sanitize() restores the sets from these before
|
|
172971
|
+
* the per-walk hook clone-guard, so a hook's in-call widening cannot
|
|
172972
|
+
* carry across calls. Null until setConfig() is called; reset by
|
|
172973
|
+
* clearConfig(). */
|
|
172974
|
+
let SET_CONFIG_ALLOWED_TAGS = null;
|
|
172975
|
+
let SET_CONFIG_ALLOWED_ATTR = null;
|
|
172704
172976
|
/* Decide if all elements (e.g. style, script) must be children of
|
|
172705
172977
|
* document.body. By default, browsers might move them to document.head */
|
|
172706
172978
|
let FORCE_BODY = false;
|
|
@@ -172743,7 +173015,17 @@ function createDOMPurify() {
|
|
|
172743
173015
|
let USE_PROFILES = {};
|
|
172744
173016
|
/* Tags to ignore content of when KEEP_CONTENT is true */
|
|
172745
173017
|
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',
|
|
173018
|
+
const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script',
|
|
173019
|
+
// <selectedcontent> mirrors the selected <option>'s subtree, cloned by
|
|
173020
|
+
// the UA (customizable <select>) — including any on* handlers — and the
|
|
173021
|
+
// engine re-mirrors synchronously whenever a removal changes which
|
|
173022
|
+
// option/selectedcontent is current, even inside DOMPurify's inert
|
|
173023
|
+
// DOMParser document. Hoisting its children on removal re-inserts a fresh
|
|
173024
|
+
// mirror target ahead of the walk, which the engine refills, looping
|
|
173025
|
+
// forever (DoS) and amplifying output. Dropping its content on removal
|
|
173026
|
+
// (rather than hoisting) breaks that cascade; the content is a duplicate
|
|
173027
|
+
// of the option, which is sanitized on its own. See campaign-3 F1/F6.
|
|
173028
|
+
'selectedcontent', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
|
|
172747
173029
|
/* Tags that are safe for data: URIs */
|
|
172748
173030
|
let DATA_URI_TAGS = null;
|
|
172749
173031
|
const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
|
|
@@ -172759,8 +173041,10 @@ function createDOMPurify() {
|
|
|
172759
173041
|
/* Allowed XHTML+XML namespaces */
|
|
172760
173042
|
let ALLOWED_NAMESPACES = null;
|
|
172761
173043
|
const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
|
|
172762
|
-
|
|
172763
|
-
let
|
|
173044
|
+
const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze(['mi', 'mo', 'mn', 'ms', 'mtext']);
|
|
173045
|
+
let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS);
|
|
173046
|
+
const DEFAULT_HTML_INTEGRATION_POINTS = freeze(['annotation-xml']);
|
|
173047
|
+
let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS);
|
|
172764
173048
|
// Certain elements are allowed in both SVG and HTML
|
|
172765
173049
|
// namespace. We need to specify them explicitly
|
|
172766
173050
|
// so that they don't get erroneously deleted from
|
|
@@ -172802,15 +173086,33 @@ function createDOMPurify() {
|
|
|
172802
173086
|
// HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
|
|
172803
173087
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
|
|
172804
173088
|
/* Set configuration parameters */
|
|
172805
|
-
ALLOWED_TAGS =
|
|
172806
|
-
|
|
172807
|
-
|
|
172808
|
-
|
|
172809
|
-
|
|
172810
|
-
|
|
172811
|
-
|
|
172812
|
-
|
|
172813
|
-
|
|
173089
|
+
ALLOWED_TAGS = _resolveSetOption(cfg, 'ALLOWED_TAGS', DEFAULT_ALLOWED_TAGS, {
|
|
173090
|
+
transform: transformCaseFunc
|
|
173091
|
+
});
|
|
173092
|
+
ALLOWED_ATTR = _resolveSetOption(cfg, 'ALLOWED_ATTR', DEFAULT_ALLOWED_ATTR, {
|
|
173093
|
+
transform: transformCaseFunc
|
|
173094
|
+
});
|
|
173095
|
+
ALLOWED_NAMESPACES = _resolveSetOption(cfg, 'ALLOWED_NAMESPACES', DEFAULT_ALLOWED_NAMESPACES, {
|
|
173096
|
+
transform: stringToString
|
|
173097
|
+
});
|
|
173098
|
+
URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, 'ADD_URI_SAFE_ATTR', DEFAULT_URI_SAFE_ATTRIBUTES, {
|
|
173099
|
+
transform: transformCaseFunc,
|
|
173100
|
+
base: DEFAULT_URI_SAFE_ATTRIBUTES
|
|
173101
|
+
});
|
|
173102
|
+
DATA_URI_TAGS = _resolveSetOption(cfg, 'ADD_DATA_URI_TAGS', DEFAULT_DATA_URI_TAGS, {
|
|
173103
|
+
transform: transformCaseFunc,
|
|
173104
|
+
base: DEFAULT_DATA_URI_TAGS
|
|
173105
|
+
});
|
|
173106
|
+
FORBID_CONTENTS = _resolveSetOption(cfg, 'FORBID_CONTENTS', DEFAULT_FORBID_CONTENTS, {
|
|
173107
|
+
transform: transformCaseFunc
|
|
173108
|
+
});
|
|
173109
|
+
FORBID_TAGS = _resolveSetOption(cfg, 'FORBID_TAGS', clone({}), {
|
|
173110
|
+
transform: transformCaseFunc
|
|
173111
|
+
});
|
|
173112
|
+
FORBID_ATTR = _resolveSetOption(cfg, 'FORBID_ATTR', clone({}), {
|
|
173113
|
+
transform: transformCaseFunc
|
|
173114
|
+
});
|
|
173115
|
+
USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === 'object' ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false;
|
|
172814
173116
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
|
|
172815
173117
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
|
|
172816
173118
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
|
|
@@ -172826,20 +173128,22 @@ function createDOMPurify() {
|
|
|
172826
173128
|
SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
|
|
172827
173129
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
|
|
172828
173130
|
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
|
-
|
|
173131
|
+
IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; // Default regexp
|
|
173132
|
+
NAMESPACE = typeof cfg.NAMESPACE === 'string' ? cfg.NAMESPACE : HTML_NAMESPACE; // Default HTML namespace
|
|
173133
|
+
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
|
|
173134
|
+
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
|
|
173135
|
+
const customElementHandling = objectHasOwnProperty(cfg, 'CUSTOM_ELEMENT_HANDLING') && cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING === 'object' ? clone(cfg.CUSTOM_ELEMENT_HANDLING) : create(null);
|
|
173136
|
+
CUSTOM_ELEMENT_HANDLING = create(null);
|
|
173137
|
+
if (objectHasOwnProperty(customElementHandling, 'tagNameCheck') && isRegexOrFunction(customElementHandling.tagNameCheck)) {
|
|
173138
|
+
CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; // Default undefined
|
|
173139
|
+
}
|
|
173140
|
+
if (objectHasOwnProperty(customElementHandling, 'attributeNameCheck') && isRegexOrFunction(customElementHandling.attributeNameCheck)) {
|
|
173141
|
+
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; // Default undefined
|
|
173142
|
+
}
|
|
173143
|
+
if (objectHasOwnProperty(customElementHandling, 'allowCustomizedBuiltInElements') && typeof customElementHandling.allowCustomizedBuiltInElements === 'boolean') {
|
|
173144
|
+
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; // Default undefined
|
|
173145
|
+
}
|
|
173146
|
+
seal(CUSTOM_ELEMENT_HANDLING);
|
|
172843
173147
|
if (SAFE_FOR_TEMPLATES) {
|
|
172844
173148
|
ALLOW_DATA_ATTR = false;
|
|
172845
173149
|
}
|
|
@@ -172875,36 +173179,36 @@ function createDOMPurify() {
|
|
|
172875
173179
|
EXTRA_ELEMENT_HANDLING.tagCheck = null;
|
|
172876
173180
|
EXTRA_ELEMENT_HANDLING.attributeCheck = null;
|
|
172877
173181
|
/* Merge configuration parameters */
|
|
172878
|
-
if (cfg
|
|
173182
|
+
if (objectHasOwnProperty(cfg, 'ADD_TAGS')) {
|
|
172879
173183
|
if (typeof cfg.ADD_TAGS === 'function') {
|
|
172880
173184
|
EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS;
|
|
172881
|
-
} else {
|
|
173185
|
+
} else if (arrayIsArray(cfg.ADD_TAGS)) {
|
|
172882
173186
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
|
|
172883
173187
|
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
172884
173188
|
}
|
|
172885
173189
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
|
|
172886
173190
|
}
|
|
172887
173191
|
}
|
|
172888
|
-
if (cfg
|
|
173192
|
+
if (objectHasOwnProperty(cfg, 'ADD_ATTR')) {
|
|
172889
173193
|
if (typeof cfg.ADD_ATTR === 'function') {
|
|
172890
173194
|
EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR;
|
|
172891
|
-
} else {
|
|
173195
|
+
} else if (arrayIsArray(cfg.ADD_ATTR)) {
|
|
172892
173196
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
|
|
172893
173197
|
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
172894
173198
|
}
|
|
172895
173199
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
|
|
172896
173200
|
}
|
|
172897
173201
|
}
|
|
172898
|
-
if (cfg.ADD_URI_SAFE_ATTR) {
|
|
173202
|
+
if (objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') && arrayIsArray(cfg.ADD_URI_SAFE_ATTR)) {
|
|
172899
173203
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
|
|
172900
173204
|
}
|
|
172901
|
-
if (cfg.FORBID_CONTENTS) {
|
|
173205
|
+
if (objectHasOwnProperty(cfg, 'FORBID_CONTENTS') && arrayIsArray(cfg.FORBID_CONTENTS)) {
|
|
172902
173206
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172903
173207
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172904
173208
|
}
|
|
172905
173209
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
|
|
172906
173210
|
}
|
|
172907
|
-
if (cfg.ADD_FORBID_CONTENTS) {
|
|
173211
|
+
if (objectHasOwnProperty(cfg, 'ADD_FORBID_CONTENTS') && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) {
|
|
172908
173212
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
|
|
172909
173213
|
FORBID_CONTENTS = clone(FORBID_CONTENTS);
|
|
172910
173214
|
}
|
|
@@ -172923,6 +173227,13 @@ function createDOMPurify() {
|
|
|
172923
173227
|
addToSet(ALLOWED_TAGS, ['tbody']);
|
|
172924
173228
|
delete FORBID_TAGS.tbody;
|
|
172925
173229
|
}
|
|
173230
|
+
// Re-derive the active Trusted Types policy from this configuration on
|
|
173231
|
+
// every parse. The active policy must never be sticky closure state that
|
|
173232
|
+
// outlives the config that set it: a caller-supplied policy left in place
|
|
173233
|
+
// after `clearConfig()` — or after a later call that supplied none, or
|
|
173234
|
+
// `TRUSTED_TYPES_POLICY: null` — could sign a subsequent "default"
|
|
173235
|
+
// `RETURN_TRUSTED_TYPE` result with a foreign, possibly unsafe policy.
|
|
173236
|
+
// See GHSA-vxr8-fq34-vvx9.
|
|
172926
173237
|
if (cfg.TRUSTED_TYPES_POLICY) {
|
|
172927
173238
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
|
|
172928
173239
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
|
|
@@ -172930,18 +173241,45 @@ function createDOMPurify() {
|
|
|
172930
173241
|
if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
|
|
172931
173242
|
throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
|
|
172932
173243
|
}
|
|
172933
|
-
//
|
|
173244
|
+
// A caller-supplied policy applies to this configuration only.
|
|
173245
|
+
const previousTrustedTypesPolicy = trustedTypesPolicy;
|
|
172934
173246
|
trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
|
|
172935
|
-
// Sign local variables required by `sanitize`.
|
|
172936
|
-
|
|
173247
|
+
// Sign local variables required by `sanitize`. If the supplied policy's
|
|
173248
|
+
// `createHTML` is circular (i.e. it calls `DOMPurify.sanitize`), this
|
|
173249
|
+
// throws via the re-entrancy guard. Restore the previous policy first so
|
|
173250
|
+
// the instance is not left in a poisoned state. See #1422.
|
|
173251
|
+
try {
|
|
173252
|
+
emptyHTML = _createTrustedHTML('');
|
|
173253
|
+
} catch (error) {
|
|
173254
|
+
trustedTypesPolicy = previousTrustedTypesPolicy;
|
|
173255
|
+
throw error;
|
|
173256
|
+
}
|
|
173257
|
+
} else if (cfg.TRUSTED_TYPES_POLICY === null) {
|
|
173258
|
+
// Explicit opt-out for this call: perform no Trusted Types signing and
|
|
173259
|
+
// create nothing (so a strict `trusted-types` CSP that disallows a
|
|
173260
|
+
// `dompurify` policy can still call `sanitize` from inside its own
|
|
173261
|
+
// policy — see #1422). Resetting to `undefined` rather than a sticky
|
|
173262
|
+
// `null` also drops any previously retained caller policy, so it cannot
|
|
173263
|
+
// resurface on a later call, while still allowing the next config-less
|
|
173264
|
+
// call to restore the internal default policy. See GHSA-vxr8-fq34-vvx9.
|
|
173265
|
+
trustedTypesPolicy = undefined;
|
|
173266
|
+
emptyHTML = '';
|
|
172937
173267
|
} else {
|
|
172938
|
-
//
|
|
173268
|
+
// No policy supplied: keep the currently active policy if one is set — a
|
|
173269
|
+
// previously supplied policy is intentionally sticky across config-less
|
|
173270
|
+
// calls — otherwise fall back to the instance's own internal policy,
|
|
173271
|
+
// created at most once. (A policy supplied for a *single* call still
|
|
173272
|
+
// lingers by design; what must not linger is a policy whose configuration
|
|
173273
|
+
// has been torn down via `clearConfig()`, which restores the default.)
|
|
172939
173274
|
if (trustedTypesPolicy === undefined) {
|
|
172940
|
-
trustedTypesPolicy =
|
|
173275
|
+
trustedTypesPolicy = _getDefaultTrustedTypesPolicy();
|
|
172941
173276
|
}
|
|
172942
|
-
//
|
|
172943
|
-
|
|
172944
|
-
|
|
173277
|
+
// Sign internal variables only when a policy is active. A falsy policy
|
|
173278
|
+
// (Trusted Types unsupported, creation failed, or an explicit opt-out)
|
|
173279
|
+
// leaves `emptyHTML` as a plain string, so we never call `.createHTML` on
|
|
173280
|
+
// a non-policy and throw. See #1422.
|
|
173281
|
+
if (trustedTypesPolicy && typeof emptyHTML === 'string') {
|
|
173282
|
+
emptyHTML = _createTrustedHTML('');
|
|
172945
173283
|
}
|
|
172946
173284
|
}
|
|
172947
173285
|
// Prevent further manipulation of configuration.
|
|
@@ -172956,6 +173294,77 @@ function createDOMPurify() {
|
|
|
172956
173294
|
* correctly. */
|
|
172957
173295
|
const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
|
|
172958
173296
|
const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
|
|
173297
|
+
/**
|
|
173298
|
+
* Namespace rules for an element in the SVG namespace.
|
|
173299
|
+
*
|
|
173300
|
+
* @param tagName the element's lowercase tag name
|
|
173301
|
+
* @param parent the (possibly simulated) parent node
|
|
173302
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173303
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173304
|
+
*/
|
|
173305
|
+
const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) {
|
|
173306
|
+
// The only way to switch from HTML namespace to SVG
|
|
173307
|
+
// is via <svg>. If it happens via any other tag, then
|
|
173308
|
+
// it should be killed.
|
|
173309
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
173310
|
+
return tagName === 'svg';
|
|
173311
|
+
}
|
|
173312
|
+
// The only way to switch from MathML to SVG is via <svg>
|
|
173313
|
+
// if the parent is either <annotation-xml> or a MathML
|
|
173314
|
+
// text integration point.
|
|
173315
|
+
if (parent.namespaceURI === MATHML_NAMESPACE) {
|
|
173316
|
+
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
|
|
173317
|
+
}
|
|
173318
|
+
// We only allow elements that are defined in SVG
|
|
173319
|
+
// spec. All others are disallowed in SVG namespace.
|
|
173320
|
+
return Boolean(ALL_SVG_TAGS[tagName]);
|
|
173321
|
+
};
|
|
173322
|
+
/**
|
|
173323
|
+
* Namespace rules for an element in the MathML namespace.
|
|
173324
|
+
*
|
|
173325
|
+
* @param tagName the element's lowercase tag name
|
|
173326
|
+
* @param parent the (possibly simulated) parent node
|
|
173327
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173328
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173329
|
+
*/
|
|
173330
|
+
const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) {
|
|
173331
|
+
// The only way to switch from HTML namespace to MathML
|
|
173332
|
+
// is via <math>. If it happens via any other tag, then
|
|
173333
|
+
// it should be killed.
|
|
173334
|
+
if (parent.namespaceURI === HTML_NAMESPACE) {
|
|
173335
|
+
return tagName === 'math';
|
|
173336
|
+
}
|
|
173337
|
+
// The only way to switch from SVG to MathML is via
|
|
173338
|
+
// <math> and HTML integration points
|
|
173339
|
+
if (parent.namespaceURI === SVG_NAMESPACE) {
|
|
173340
|
+
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
|
|
173341
|
+
}
|
|
173342
|
+
// We only allow elements that are defined in MathML
|
|
173343
|
+
// spec. All others are disallowed in MathML namespace.
|
|
173344
|
+
return Boolean(ALL_MATHML_TAGS[tagName]);
|
|
173345
|
+
};
|
|
173346
|
+
/**
|
|
173347
|
+
* Namespace rules for an element in the HTML namespace.
|
|
173348
|
+
*
|
|
173349
|
+
* @param tagName the element's lowercase tag name
|
|
173350
|
+
* @param parent the (possibly simulated) parent node
|
|
173351
|
+
* @param parentTagName the parent's lowercase tag name
|
|
173352
|
+
* @returns true if a spec-compliant parser could produce this element
|
|
173353
|
+
*/
|
|
173354
|
+
const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) {
|
|
173355
|
+
// The only way to switch from SVG to HTML is via
|
|
173356
|
+
// HTML integration points, and from MathML to HTML
|
|
173357
|
+
// is via MathML text integration points
|
|
173358
|
+
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
|
|
173359
|
+
return false;
|
|
173360
|
+
}
|
|
173361
|
+
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
|
|
173362
|
+
return false;
|
|
173363
|
+
}
|
|
173364
|
+
// We disallow tags that are specific for MathML
|
|
173365
|
+
// or SVG and should never appear in HTML namespace
|
|
173366
|
+
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
|
|
173367
|
+
};
|
|
172959
173368
|
/**
|
|
172960
173369
|
* @param element a DOM element whose namespace is being checked
|
|
172961
173370
|
* @returns Return false if the element has a
|
|
@@ -172978,51 +173387,13 @@ function createDOMPurify() {
|
|
|
172978
173387
|
return false;
|
|
172979
173388
|
}
|
|
172980
173389
|
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]);
|
|
173390
|
+
return _checkSvgNamespace(tagName, parent, parentTagName);
|
|
172996
173391
|
}
|
|
172997
173392
|
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]);
|
|
173393
|
+
return _checkMathMlNamespace(tagName, parent, parentTagName);
|
|
173012
173394
|
}
|
|
173013
173395
|
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]);
|
|
173396
|
+
return _checkHtmlNamespace(tagName, parent, parentTagName);
|
|
173026
173397
|
}
|
|
173027
173398
|
// For XHTML and XML documents that support custom namespaces
|
|
173028
173399
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
|
|
@@ -173047,7 +173418,74 @@ function createDOMPurify() {
|
|
|
173047
173418
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove
|
|
173048
173419
|
getParentNode(node).removeChild(node);
|
|
173049
173420
|
} catch (_) {
|
|
173421
|
+
/* The normal detach failed — this is reached for a parentless node
|
|
173422
|
+
(getParentNode() is null, so .removeChild throws). Element.prototype
|
|
173423
|
+
.remove() is itself a spec no-op on a parentless node, so a recorded
|
|
173424
|
+
"removal" would otherwise hand the caller back an intact,
|
|
173425
|
+
payload-bearing node (e.g. a detached IN_PLACE root the mXSS canary or
|
|
173426
|
+
the style-with-element-child rule decided to kill). Fail closed by
|
|
173427
|
+
throwing — exactly as a clobbered root does at the IN_PLACE entry —
|
|
173428
|
+
rather than trying to "neutralize" the node via its own methods.
|
|
173429
|
+
Neutralizing would mean calling getAttributeNames()/removeAttribute()
|
|
173430
|
+
on the node, both of which a <form> root can clobber via a named child
|
|
173431
|
+
(and _isClobbered does not even probe getAttributeNames), so the
|
|
173432
|
+
neutralize step could itself be silently defeated, leaving the payload
|
|
173433
|
+
intact. A throw touches only the cached, clobber-safe remove() and
|
|
173434
|
+
getParentNode(). Generalizes GHSA-r47g-fvhr-h676 (clobbered-form root)
|
|
173435
|
+
to every root-kill reason. REPORT-3.
|
|
173436
|
+
This lives inside the catch, so it never fires for a normally-removed
|
|
173437
|
+
in-tree node: those have a parent, removeChild() succeeds, and the
|
|
173438
|
+
catch is not entered. Only a kept (parentless) root reaches here. */
|
|
173050
173439
|
remove(node);
|
|
173440
|
+
if (!getParentNode(node)) {
|
|
173441
|
+
throw typeErrorCreate('a node selected for removal could not be detached from its tree ' + 'and cannot be safely returned; refusing to sanitize in place');
|
|
173442
|
+
}
|
|
173443
|
+
}
|
|
173444
|
+
};
|
|
173445
|
+
/**
|
|
173446
|
+
* _neutralizeRoot
|
|
173447
|
+
*
|
|
173448
|
+
* Fail-closed teardown of an in-place root after the sanitize walk aborts
|
|
173449
|
+
* (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
|
|
173450
|
+
* custom element's reaction detaches a node so `_forceRemove`'s deliberate
|
|
173451
|
+
* parentless guard throws, or any other re-entrant engine mutation — would
|
|
173452
|
+
* otherwise leave the caller's *live* tree half-sanitized, with everything
|
|
173453
|
+
* after the abort point still carrying its handlers. There is no safe way
|
|
173454
|
+
* to resume the walk (the tree mutated under us), so we strip the root bare:
|
|
173455
|
+
* remove every child and every attribute, then let the caller's catch see
|
|
173456
|
+
* the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
|
|
173457
|
+
* getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
|
|
173458
|
+
*
|
|
173459
|
+
* @param root the in-place root to empty
|
|
173460
|
+
*/
|
|
173461
|
+
const _neutralizeRoot = function _neutralizeRoot(root) {
|
|
173462
|
+
const childNodes = getChildNodes(root);
|
|
173463
|
+
if (childNodes) {
|
|
173464
|
+
const snapshot = [];
|
|
173465
|
+
arrayForEach(childNodes, child => {
|
|
173466
|
+
arrayPush(snapshot, child);
|
|
173467
|
+
});
|
|
173468
|
+
arrayForEach(snapshot, child => {
|
|
173469
|
+
try {
|
|
173470
|
+
remove(child);
|
|
173471
|
+
} catch (_) {
|
|
173472
|
+
/* Best-effort teardown; a still-attached child is handled below */
|
|
173473
|
+
}
|
|
173474
|
+
});
|
|
173475
|
+
}
|
|
173476
|
+
const attributes = getAttributes(root);
|
|
173477
|
+
if (attributes) {
|
|
173478
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
173479
|
+
const attribute = attributes[i];
|
|
173480
|
+
const name = attribute && attribute.name;
|
|
173481
|
+
if (typeof name === 'string') {
|
|
173482
|
+
try {
|
|
173483
|
+
root.removeAttribute(name);
|
|
173484
|
+
} catch (_) {
|
|
173485
|
+
/* Clobbered removeAttribute — ignore (fail-closed best effort) */
|
|
173486
|
+
}
|
|
173487
|
+
}
|
|
173488
|
+
}
|
|
173051
173489
|
}
|
|
173052
173490
|
};
|
|
173053
173491
|
/**
|
|
@@ -173082,6 +173520,72 @@ function createDOMPurify() {
|
|
|
173082
173520
|
}
|
|
173083
173521
|
}
|
|
173084
173522
|
};
|
|
173523
|
+
/**
|
|
173524
|
+
* _stripDisallowedAttributes
|
|
173525
|
+
*
|
|
173526
|
+
* Removes every attribute the active configuration does not allow from a
|
|
173527
|
+
* single element, using the same allowlist as the main attribute pass (so
|
|
173528
|
+
* `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
|
|
173529
|
+
* neutralise nodes that are being discarded from an in-place tree.
|
|
173530
|
+
*
|
|
173531
|
+
* @param element the element to strip
|
|
173532
|
+
*/
|
|
173533
|
+
const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) {
|
|
173534
|
+
const attributes = getAttributes(element);
|
|
173535
|
+
if (!attributes) {
|
|
173536
|
+
return;
|
|
173537
|
+
}
|
|
173538
|
+
for (let i = attributes.length - 1; i >= 0; --i) {
|
|
173539
|
+
const attribute = attributes[i];
|
|
173540
|
+
const name = attribute && attribute.name;
|
|
173541
|
+
if (typeof name !== 'string' || ALLOWED_ATTR[transformCaseFunc(name)]) {
|
|
173542
|
+
continue;
|
|
173543
|
+
}
|
|
173544
|
+
try {
|
|
173545
|
+
element.removeAttribute(name);
|
|
173546
|
+
} catch (_) {
|
|
173547
|
+
/* Clobbered removeAttribute on a doomed node — ignore */
|
|
173548
|
+
}
|
|
173549
|
+
}
|
|
173550
|
+
};
|
|
173551
|
+
/**
|
|
173552
|
+
* _neutralizeSubtree
|
|
173553
|
+
*
|
|
173554
|
+
* Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
|
|
173555
|
+
* move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
|
|
173556
|
+
* namespace, comment, processing-instruction and KEEP_CONTENT:false removals
|
|
173557
|
+
* all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
|
|
173558
|
+
* those dropped nodes are detached from the caller's LIVE tree but a
|
|
173559
|
+
* handler-bearing original among them (an `<img onerror>`/`<video>` that was
|
|
173560
|
+
* loading) keeps its queued resource event, which fires in page scope after
|
|
173561
|
+
* sanitize returns. This walks a removed subtree and strips every attribute
|
|
173562
|
+
* the active configuration does not allow — so `on*` handlers are cancelled
|
|
173563
|
+
* through the SAME allowlist that governs kept nodes, not a separate `/^on/`
|
|
173564
|
+
* blocklist. Run synchronously before sanitize returns, i.e. before any
|
|
173565
|
+
* queued event can fire. Hook-free by design: these nodes leave the output,
|
|
173566
|
+
* so firing attribute hooks for them would be surprising. Clobber-safe reads;
|
|
173567
|
+
* a doomed clobbered node may shadow `removeAttribute` (its own attributes are
|
|
173568
|
+
* irrelevant — it is discarded — while its non-clobbered descendants, e.g.
|
|
173569
|
+
* the `<img>`, are reached and scrubbed).
|
|
173570
|
+
*
|
|
173571
|
+
* @param root the root of a removed subtree to neutralise
|
|
173572
|
+
*/
|
|
173573
|
+
const _neutralizeSubtree = function _neutralizeSubtree(root) {
|
|
173574
|
+
const stack = [root];
|
|
173575
|
+
while (stack.length > 0) {
|
|
173576
|
+
const node = stack.pop();
|
|
173577
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
173578
|
+
if (nodeType === NODE_TYPE.element) {
|
|
173579
|
+
_stripDisallowedAttributes(node);
|
|
173580
|
+
}
|
|
173581
|
+
const childNodes = getChildNodes(node);
|
|
173582
|
+
if (childNodes) {
|
|
173583
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
173584
|
+
stack.push(childNodes[i]);
|
|
173585
|
+
}
|
|
173586
|
+
}
|
|
173587
|
+
}
|
|
173588
|
+
};
|
|
173085
173589
|
/**
|
|
173086
173590
|
* _initDocument
|
|
173087
173591
|
*
|
|
@@ -173103,7 +173607,7 @@ function createDOMPurify() {
|
|
|
173103
173607
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
|
|
173104
173608
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
|
|
173105
173609
|
}
|
|
173106
|
-
const dirtyPayload = trustedTypesPolicy ?
|
|
173610
|
+
const dirtyPayload = trustedTypesPolicy ? _createTrustedHTML(dirty) : dirty;
|
|
173107
173611
|
/*
|
|
173108
173612
|
* Use the DOMParser API by default, fallback later if needs be
|
|
173109
173613
|
* DOMParser not work for svg when has multiple root element.
|
|
@@ -173143,29 +173647,254 @@ function createDOMPurify() {
|
|
|
173143
173647
|
// eslint-disable-next-line no-bitwise
|
|
173144
173648
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
|
|
173145
173649
|
};
|
|
173650
|
+
/**
|
|
173651
|
+
* Replace template expression syntax (mustache, ERB, template
|
|
173652
|
+
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
|
|
173653
|
+
* sites. Order matters: mustache, then ERB, then template literal.
|
|
173654
|
+
*
|
|
173655
|
+
* @param value the string to scrub
|
|
173656
|
+
* @returns the scrubbed string
|
|
173657
|
+
*/
|
|
173658
|
+
const _stripTemplateExpressions = function _stripTemplateExpressions(value) {
|
|
173659
|
+
value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
|
|
173660
|
+
value = stringReplace(value, ERB_EXPR$1, ' ');
|
|
173661
|
+
value = stringReplace(value, TMPLIT_EXPR$1, ' ');
|
|
173662
|
+
return value;
|
|
173663
|
+
};
|
|
173664
|
+
/**
|
|
173665
|
+
* Strip template-engine expressions ({{...}}, ${...}, <%...%>) from the
|
|
173666
|
+
* character data of an element subtree. Used as the final safety net for
|
|
173667
|
+
* SAFE_FOR_TEMPLATES on every DOM-returning code path so that expressions
|
|
173668
|
+
* which only form after text-node normalization (e.g. fragments split across
|
|
173669
|
+
* stripped elements) cannot survive into a template-evaluating framework.
|
|
173670
|
+
*
|
|
173671
|
+
* Walks text/comment/CDATA/processing-instruction nodes and mutates `.data`
|
|
173672
|
+
* in place rather than round-tripping through innerHTML. This preserves
|
|
173673
|
+
* descendant node references (important for IN_PLACE callers), avoids a
|
|
173674
|
+
* serialize/reparse cycle, and reads literal character data — which means
|
|
173675
|
+
* `<%...%>` in text content matches the ERB regex against its real bytes
|
|
173676
|
+
* instead of the HTML-entity-escaped form innerHTML would produce.
|
|
173677
|
+
*
|
|
173678
|
+
* Attribute values are not visited here; SAFE_FOR_TEMPLATES handling for
|
|
173679
|
+
* attributes is performed during the per-node `_sanitizeAttributes` pass.
|
|
173680
|
+
*
|
|
173681
|
+
* @param node The root element whose character data should be scrubbed.
|
|
173682
|
+
*/
|
|
173683
|
+
const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
|
|
173684
|
+
var _node$querySelectorAl;
|
|
173685
|
+
node.normalize();
|
|
173686
|
+
const walker = createNodeIterator.call(node.ownerDocument || node, node,
|
|
173687
|
+
// eslint-disable-next-line no-bitwise
|
|
173688
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
|
|
173689
|
+
let currentNode = walker.nextNode();
|
|
173690
|
+
while (currentNode) {
|
|
173691
|
+
currentNode.data = _stripTemplateExpressions(currentNode.data);
|
|
173692
|
+
currentNode = walker.nextNode();
|
|
173693
|
+
}
|
|
173694
|
+
// NodeIterator does not descend into <template>.content per the DOM spec,
|
|
173695
|
+
// so we must explicitly recurse into each template's content fragment,
|
|
173696
|
+
// mirroring the approach used by _sanitizeShadowDOM.
|
|
173697
|
+
const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, 'template');
|
|
173698
|
+
if (templates) {
|
|
173699
|
+
arrayForEach(templates, tmpl => {
|
|
173700
|
+
if (_isDocumentFragment(tmpl.content)) {
|
|
173701
|
+
_scrubTemplateExpressions2(tmpl.content);
|
|
173702
|
+
}
|
|
173703
|
+
});
|
|
173704
|
+
}
|
|
173705
|
+
};
|
|
173146
173706
|
/**
|
|
173147
173707
|
* _isClobbered
|
|
173148
173708
|
*
|
|
173709
|
+
* Detect DOM-clobbering on HTMLFormElement nodes. Form is the only HTML
|
|
173710
|
+
* interface with [LegacyOverrideBuiltIns]; a descendant element with a
|
|
173711
|
+
* `name` attribute matching a prototype property shadows that property
|
|
173712
|
+
* on direct reads. We use this check at the IN_PLACE entry-point and
|
|
173713
|
+
* during attribute sanitization to refuse clobbered forms.
|
|
173714
|
+
*
|
|
173149
173715
|
* @param element element to check for clobbering attacks
|
|
173150
173716
|
* @return true if clobbered, false if safe
|
|
173151
173717
|
*/
|
|
173152
173718
|
const _isClobbered = function _isClobbered(element) {
|
|
173153
|
-
|
|
173719
|
+
// Realm-independent tag-name probe. If we can't determine the tag
|
|
173720
|
+
// name at all, we can't reason about clobbering — return false
|
|
173721
|
+
// (the caller's other defences still apply).
|
|
173722
|
+
const realTagName = getNodeName ? getNodeName(element) : null;
|
|
173723
|
+
if (typeof realTagName !== 'string') {
|
|
173724
|
+
return false;
|
|
173725
|
+
}
|
|
173726
|
+
if (transformCaseFunc(realTagName) !== 'form') {
|
|
173727
|
+
return false;
|
|
173728
|
+
}
|
|
173729
|
+
return typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' ||
|
|
173730
|
+
// Realm-safe NamedNodeMap detection: equality against the cached
|
|
173731
|
+
// prototype getter. Clobbered .attributes (e.g. <input name="attributes">)
|
|
173732
|
+
// makes the direct read diverge from the cached read; a clean form
|
|
173733
|
+
// (same-realm OR foreign-realm) has both reads pointing at the same
|
|
173734
|
+
// canonical NamedNodeMap.
|
|
173735
|
+
element.attributes !== getAttributes(element) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function' ||
|
|
173736
|
+
// NodeType clobbering probe. Cached Node.prototype.nodeType getter
|
|
173737
|
+
// returns the integer 1 for any Element regardless of realm; direct
|
|
173738
|
+
// read on a clobbered form (e.g. <input name="nodeType">) returns
|
|
173739
|
+
// the named child element. Cheap addition — nodeType is read from
|
|
173740
|
+
// an internal slot, no serialization cost — and removes a residual
|
|
173741
|
+
// clobbering surface used by several mXSS / PI / comment branches
|
|
173742
|
+
// in _sanitizeElements that compare currentNode.nodeType directly.
|
|
173743
|
+
element.nodeType !== getNodeType(element) ||
|
|
173744
|
+
// HTMLFormElement has [LegacyOverrideBuiltIns]: a descendant named
|
|
173745
|
+
// "childNodes" shadows the prototype getter. Direct reads of
|
|
173746
|
+
// form.childNodes from a clobbered form return the named child
|
|
173747
|
+
// instead of the real NodeList, so any walk that reads it directly
|
|
173748
|
+
// skips the form's real children. Compare the direct read to the
|
|
173749
|
+
// cached Node.prototype getter — when the form's named-property
|
|
173750
|
+
// getter intercepts the read, the two values differ and we flag
|
|
173751
|
+
// the form. This catches every clobbering child type (input,
|
|
173752
|
+
// select, etc.) regardless of whether the named child happens to
|
|
173753
|
+
// carry a numeric .length, which a typeof-based probe would miss
|
|
173754
|
+
// (e.g. HTMLSelectElement.length is a defined unsigned-long).
|
|
173755
|
+
element.childNodes !== getChildNodes(element);
|
|
173756
|
+
};
|
|
173757
|
+
/**
|
|
173758
|
+
* Checks whether the given value is a DocumentFragment from any realm.
|
|
173759
|
+
*
|
|
173760
|
+
* The realm-independent replacement reads `nodeType` through the cached
|
|
173761
|
+
* Node.prototype getter and compares to the DOCUMENT_FRAGMENT_NODE
|
|
173762
|
+
* constant (11). nodeType is a numeric value resolved from the node's
|
|
173763
|
+
* internal slot, identical across realms for the same kind of node.
|
|
173764
|
+
*
|
|
173765
|
+
* @param value object to check
|
|
173766
|
+
* @return true if value is a DocumentFragment-shaped node from any realm
|
|
173767
|
+
*/
|
|
173768
|
+
const _isDocumentFragment = function _isDocumentFragment(value) {
|
|
173769
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
173770
|
+
return false;
|
|
173771
|
+
}
|
|
173772
|
+
try {
|
|
173773
|
+
return getNodeType(value) === NODE_TYPE.documentFragment;
|
|
173774
|
+
} catch (_) {
|
|
173775
|
+
return false;
|
|
173776
|
+
}
|
|
173154
173777
|
};
|
|
173155
173778
|
/**
|
|
173156
|
-
* Checks whether the given object is a DOM node
|
|
173779
|
+
* Checks whether the given object is a DOM node, including nodes that
|
|
173780
|
+
* originate from a different window/realm (e.g. an iframe's
|
|
173781
|
+
* contentDocument). The previous `value instanceof Node` check was
|
|
173782
|
+
* realm-bound: nodes from a different window failed it, causing
|
|
173783
|
+
* sanitize() to silently stringify them and reset IN_PLACE to false,
|
|
173784
|
+
* returning the original node unsanitized. See GHSA-4w3q-35jp-p934.
|
|
173157
173785
|
*
|
|
173158
173786
|
* @param value object to check whether it's a DOM node
|
|
173159
|
-
* @return true
|
|
173787
|
+
* @return true if value is a DOM node from any realm
|
|
173160
173788
|
*/
|
|
173161
173789
|
const _isNode = function _isNode(value) {
|
|
173162
|
-
|
|
173790
|
+
if (!getNodeType || typeof value !== 'object' || value === null) {
|
|
173791
|
+
return false;
|
|
173792
|
+
}
|
|
173793
|
+
try {
|
|
173794
|
+
return typeof getNodeType(value) === 'number';
|
|
173795
|
+
} catch (_) {
|
|
173796
|
+
return false;
|
|
173797
|
+
}
|
|
173163
173798
|
};
|
|
173164
173799
|
function _executeHooks(hooks, currentNode, data) {
|
|
173800
|
+
if (hooks.length === 0) {
|
|
173801
|
+
return;
|
|
173802
|
+
}
|
|
173165
173803
|
arrayForEach(hooks, hook => {
|
|
173166
173804
|
hook.call(DOMPurify, currentNode, data, CONFIG);
|
|
173167
173805
|
});
|
|
173168
173806
|
}
|
|
173807
|
+
/**
|
|
173808
|
+
* Structural-threat checks that condemn a node regardless of the
|
|
173809
|
+
* allowlists: mXSS via namespace confusion, risky CSS construction,
|
|
173810
|
+
* processing instructions, markup-bearing comments. Pure predicate;
|
|
173811
|
+
* the caller removes. Check order is load-bearing.
|
|
173812
|
+
*
|
|
173813
|
+
* @param currentNode the node to inspect
|
|
173814
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
173815
|
+
* @return true if the node must be removed
|
|
173816
|
+
*/
|
|
173817
|
+
const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
|
|
173818
|
+
/* Detect mXSS attempts abusing namespace confusion */
|
|
173819
|
+
if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) {
|
|
173820
|
+
return true;
|
|
173821
|
+
}
|
|
173822
|
+
/* Remove risky CSS construction leading to mXSS */
|
|
173823
|
+
if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && tagName === 'style' && _isNode(currentNode.firstElementChild)) {
|
|
173824
|
+
return true;
|
|
173825
|
+
}
|
|
173826
|
+
/* Remove any occurrence of processing instructions */
|
|
173827
|
+
if (currentNode.nodeType === NODE_TYPE.processingInstruction) {
|
|
173828
|
+
return true;
|
|
173829
|
+
}
|
|
173830
|
+
/* Remove any kind of possibly harmful comments */
|
|
173831
|
+
if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) {
|
|
173832
|
+
return true;
|
|
173833
|
+
}
|
|
173834
|
+
return false;
|
|
173835
|
+
};
|
|
173836
|
+
/**
|
|
173837
|
+
* Handle a node whose tag is forbidden or not allowlisted: keep
|
|
173838
|
+
* allowed custom elements (false return exits _sanitizeElements
|
|
173839
|
+
* early - namespace/fallback checks and the afterSanitizeElements
|
|
173840
|
+
* hook are intentionally skipped for kept custom elements), else
|
|
173841
|
+
* hoist content per KEEP_CONTENT and remove.
|
|
173842
|
+
*
|
|
173843
|
+
* @param currentNode the disallowed node
|
|
173844
|
+
* @param tagName the node's transformCaseFunc'd tag name
|
|
173845
|
+
* @return true if the node was removed, false if kept
|
|
173846
|
+
*/
|
|
173847
|
+
const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName) {
|
|
173848
|
+
/* Check if we have a custom element to handle */
|
|
173849
|
+
if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
|
|
173850
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
|
|
173851
|
+
return false;
|
|
173852
|
+
}
|
|
173853
|
+
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
|
|
173854
|
+
return false;
|
|
173855
|
+
}
|
|
173856
|
+
}
|
|
173857
|
+
/* Keep content except for bad-listed elements.
|
|
173858
|
+
Use the cached prototype getters exclusively — the previous code
|
|
173859
|
+
had `|| currentNode.parentNode` / `|| currentNode.childNodes`
|
|
173860
|
+
fallbacks, but the cached getters always return the canonical
|
|
173861
|
+
value (or null for a real parent-less node), so the fallback
|
|
173862
|
+
path was dead in safe cases and a clobbering surface in unsafe
|
|
173863
|
+
ones. Falsy cached results stay falsy; the `if (childNodes &&
|
|
173864
|
+
parentNode)` check already gates correctly. */
|
|
173865
|
+
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
|
|
173866
|
+
const parentNode = getParentNode(currentNode);
|
|
173867
|
+
const childNodes = getChildNodes(currentNode);
|
|
173868
|
+
if (childNodes && parentNode) {
|
|
173869
|
+
const childCount = childNodes.length;
|
|
173870
|
+
/* In-place: hoist the *original* children so the iterator visits
|
|
173871
|
+
and sanitises them through the same allowlist pass as every other
|
|
173872
|
+
node. The caller built the tree in the live document, so the
|
|
173873
|
+
originals carry already-queued resource events (`<img onerror>`,
|
|
173874
|
+
`<video>`/`<audio>` error, lazy/`onload`, …); cloning would leave
|
|
173875
|
+
those originals detached but still armed, firing in page scope
|
|
173876
|
+
while the returned tree looked clean. Moving is safe in-place: the
|
|
173877
|
+
root is pre-validated as an allowed tag and so is never the node
|
|
173878
|
+
being removed, which keeps `parentNode` inside the iterator root
|
|
173879
|
+
and the relocated child inside the serialised tree.
|
|
173880
|
+
Otherwise (string / DOM-copy paths): clone. The iterator is rooted
|
|
173881
|
+
at — and the result serialised from — `body`, so a restrictive
|
|
173882
|
+
ALLOWED_TAGS that removes `body` itself must leave its content in
|
|
173883
|
+
place, which only cloning does; and those paths parse into an
|
|
173884
|
+
inert document, so their discarded originals never had a queued
|
|
173885
|
+
event to neutralise.
|
|
173886
|
+
`childNodes` is live; a tail-to-head walk keeps `childNodes[i]`
|
|
173887
|
+
valid whether we move (drops the trailing entry) or clone (leaves
|
|
173888
|
+
the list intact). */
|
|
173889
|
+
for (let i = childCount - 1; i >= 0; --i) {
|
|
173890
|
+
const hoisted = IN_PLACE ? childNodes[i] : cloneNode(childNodes[i], true);
|
|
173891
|
+
parentNode.insertBefore(hoisted, getNextSibling(currentNode));
|
|
173892
|
+
}
|
|
173893
|
+
}
|
|
173894
|
+
}
|
|
173895
|
+
_forceRemove(currentNode);
|
|
173896
|
+
return true;
|
|
173897
|
+
};
|
|
173169
173898
|
/**
|
|
173170
173899
|
* _sanitizeElements
|
|
173171
173900
|
*
|
|
@@ -173176,7 +173905,6 @@ function createDOMPurify() {
|
|
|
173176
173905
|
* @return true if node was killed, false if left alive
|
|
173177
173906
|
*/
|
|
173178
173907
|
const _sanitizeElements = function _sanitizeElements(currentNode) {
|
|
173179
|
-
let content = null;
|
|
173180
173908
|
/* Execute a hook if present */
|
|
173181
173909
|
_executeHooks(hooks.beforeSanitizeElements, currentNode, null);
|
|
173182
173910
|
/* Check if element is clobbered or can clobber */
|
|
@@ -173185,76 +173913,41 @@ function createDOMPurify() {
|
|
|
173185
173913
|
return true;
|
|
173186
173914
|
}
|
|
173187
173915
|
/* Now let's check the element's type and name */
|
|
173188
|
-
const tagName = transformCaseFunc(currentNode.nodeName);
|
|
173916
|
+
const tagName = transformCaseFunc(getNodeName ? getNodeName(currentNode) : currentNode.nodeName);
|
|
173189
173917
|
/* Execute a hook if present */
|
|
173190
173918
|
_executeHooks(hooks.uponSanitizeElement, currentNode, {
|
|
173191
173919
|
tagName,
|
|
173192
173920
|
allowedTags: ALLOWED_TAGS
|
|
173193
173921
|
});
|
|
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)) {
|
|
173922
|
+
/* Remove mXSS vectors, processing instructions and risky comments */
|
|
173923
|
+
if (_isUnsafeNode(currentNode, tagName)) {
|
|
173211
173924
|
_forceRemove(currentNode);
|
|
173212
173925
|
return true;
|
|
173213
173926
|
}
|
|
173214
173927
|
/* Remove element if anything forbids its presence */
|
|
173215
173928
|
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)) {
|
|
173929
|
+
return _sanitizeDisallowedNode(currentNode, tagName);
|
|
173930
|
+
}
|
|
173931
|
+
/* Check whether element has a valid namespace.
|
|
173932
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use the cached Node.prototype
|
|
173933
|
+
nodeType getter rather than `instanceof Element`, which is realm-
|
|
173934
|
+
bound and short-circuits to false for any node minted in a different
|
|
173935
|
+
realm — letting a foreign-realm element with a forbidden namespace
|
|
173936
|
+
slip past the namespace check entirely. */
|
|
173937
|
+
const nt = getNodeType ? getNodeType(currentNode) : currentNode.nodeType;
|
|
173938
|
+
if (nt === NODE_TYPE.element && !_checkValidNamespace(currentNode)) {
|
|
173243
173939
|
_forceRemove(currentNode);
|
|
173244
173940
|
return true;
|
|
173245
173941
|
}
|
|
173246
173942
|
/* Make sure that older browsers don't get fallback-tag mXSS */
|
|
173247
|
-
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(
|
|
173943
|
+
if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(FALLBACK_TAG_CLOSE, currentNode.innerHTML)) {
|
|
173248
173944
|
_forceRemove(currentNode);
|
|
173249
173945
|
return true;
|
|
173250
173946
|
}
|
|
173251
173947
|
/* Sanitize element content to be template-safe */
|
|
173252
173948
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
|
|
173253
173949
|
/* 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
|
-
});
|
|
173950
|
+
const content = _stripTemplateExpressions(currentNode.textContent);
|
|
173258
173951
|
if (currentNode.textContent !== content) {
|
|
173259
173952
|
arrayPush(DOMPurify.removed, {
|
|
173260
173953
|
element: currentNode.cloneNode()
|
|
@@ -173284,11 +173977,12 @@ function createDOMPurify() {
|
|
|
173284
173977
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
|
|
173285
173978
|
return false;
|
|
173286
173979
|
}
|
|
173980
|
+
const nameIsPermitted = ALLOWED_ATTR[lcName] || EXTRA_ELEMENT_HANDLING.attributeCheck instanceof Function && EXTRA_ELEMENT_HANDLING.attributeCheck(lcName, lcTag);
|
|
173287
173981
|
/* Allow valid data-* attributes: At least one character after "-"
|
|
173288
173982
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
|
|
173289
173983
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
|
|
173290
173984
|
We don't need to check the value; it's always URI safe. */
|
|
173291
|
-
if (ALLOW_DATA_ATTR &&
|
|
173985
|
+
if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!nameIsPermitted) {
|
|
173292
173986
|
if (
|
|
173293
173987
|
// First condition does a very basic check if a) it's basically a valid custom element tagname AND
|
|
173294
173988
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
|
|
@@ -173300,11 +173994,15 @@ function createDOMPurify() {
|
|
|
173300
173994
|
return false;
|
|
173301
173995
|
}
|
|
173302
173996
|
/* 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) {
|
|
173997
|
+
} 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
173998
|
return false;
|
|
173305
173999
|
} else ;
|
|
173306
174000
|
return true;
|
|
173307
174001
|
};
|
|
174002
|
+
/* Names the HTML spec reserves from valid-custom-element-name; these must
|
|
174003
|
+
* never be treated as basic custom elements even when a permissive
|
|
174004
|
+
* CUSTOM_ELEMENT_HANDLING.tagNameCheck is configured. */
|
|
174005
|
+
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
174006
|
/**
|
|
173309
174007
|
* _isBasicCustomElement
|
|
173310
174008
|
* checks if at least one dash is included in tagName, and it's not the first char
|
|
@@ -173314,7 +174012,64 @@ function createDOMPurify() {
|
|
|
173314
174012
|
* @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
|
|
173315
174013
|
*/
|
|
173316
174014
|
const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
|
|
173317
|
-
return tagName
|
|
174015
|
+
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
|
|
174016
|
+
};
|
|
174017
|
+
/**
|
|
174018
|
+
* Wrap an attribute value in the matching Trusted Types object when
|
|
174019
|
+
* the active policy requires it. Namespaced attributes pass through
|
|
174020
|
+
* unchanged (no TT support yet, see
|
|
174021
|
+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
|
|
174022
|
+
*
|
|
174023
|
+
* @param lcTag lowercase tag name of the containing element
|
|
174024
|
+
* @param lcName lowercase attribute name
|
|
174025
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
174026
|
+
* @param value the attribute value to wrap
|
|
174027
|
+
* @return the value, wrapped when Trusted Types demand it
|
|
174028
|
+
*/
|
|
174029
|
+
const _applyTrustedTypesToAttribute = function _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value) {
|
|
174030
|
+
if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function' && !namespaceURI) {
|
|
174031
|
+
switch (trustedTypes.getAttributeType(lcTag, lcName)) {
|
|
174032
|
+
case 'TrustedHTML':
|
|
174033
|
+
{
|
|
174034
|
+
return _createTrustedHTML(value);
|
|
174035
|
+
}
|
|
174036
|
+
case 'TrustedScriptURL':
|
|
174037
|
+
{
|
|
174038
|
+
return _createTrustedScriptURL(value);
|
|
174039
|
+
}
|
|
174040
|
+
}
|
|
174041
|
+
}
|
|
174042
|
+
return value;
|
|
174043
|
+
};
|
|
174044
|
+
/**
|
|
174045
|
+
* Write a modified attribute value back onto the element. On
|
|
174046
|
+
* success, re-probe for clobbering introduced by the new value and
|
|
174047
|
+
* remove the element when found; otherwise pop the removal entry
|
|
174048
|
+
* recorded by the earlier _removeAttribute (long-standing pairing
|
|
174049
|
+
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
|
|
174050
|
+
* failure, remove the attribute instead.
|
|
174051
|
+
*
|
|
174052
|
+
* @param currentNode the element carrying the attribute
|
|
174053
|
+
* @param name the attribute name as present on the element
|
|
174054
|
+
* @param namespaceURI the attribute's namespace, if any
|
|
174055
|
+
* @param value the new attribute value
|
|
174056
|
+
*/
|
|
174057
|
+
const _setAttributeValue = function _setAttributeValue(currentNode, name, namespaceURI, value) {
|
|
174058
|
+
try {
|
|
174059
|
+
if (namespaceURI) {
|
|
174060
|
+
currentNode.setAttributeNS(namespaceURI, name, value);
|
|
174061
|
+
} else {
|
|
174062
|
+
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
|
|
174063
|
+
currentNode.setAttribute(name, value);
|
|
174064
|
+
}
|
|
174065
|
+
if (_isClobbered(currentNode)) {
|
|
174066
|
+
_forceRemove(currentNode);
|
|
174067
|
+
} else {
|
|
174068
|
+
arrayPop(DOMPurify.removed);
|
|
174069
|
+
}
|
|
174070
|
+
} catch (_) {
|
|
174071
|
+
_removeAttribute(name, currentNode);
|
|
174072
|
+
}
|
|
173318
174073
|
};
|
|
173319
174074
|
/**
|
|
173320
174075
|
* _sanitizeAttributes
|
|
@@ -173329,9 +174084,7 @@ function createDOMPurify() {
|
|
|
173329
174084
|
const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
|
|
173330
174085
|
/* Execute a hook if present */
|
|
173331
174086
|
_executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
|
|
173332
|
-
const
|
|
173333
|
-
attributes
|
|
173334
|
-
} = currentNode;
|
|
174087
|
+
const attributes = currentNode.attributes;
|
|
173335
174088
|
/* Check if we have attributes; if not we might have a text node */
|
|
173336
174089
|
if (!attributes || _isClobbered(currentNode)) {
|
|
173337
174090
|
return;
|
|
@@ -173344,14 +174097,13 @@ function createDOMPurify() {
|
|
|
173344
174097
|
forceKeepAttr: undefined
|
|
173345
174098
|
};
|
|
173346
174099
|
let l = attributes.length;
|
|
174100
|
+
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173347
174101
|
/* Go backwards over all attributes; safely remove bad ones */
|
|
173348
174102
|
while (l--) {
|
|
173349
174103
|
const attr = attributes[l];
|
|
173350
|
-
const
|
|
173351
|
-
|
|
173352
|
-
|
|
173353
|
-
value: attrValue
|
|
173354
|
-
} = attr;
|
|
174104
|
+
const name = attr.name,
|
|
174105
|
+
namespaceURI = attr.namespaceURI,
|
|
174106
|
+
attrValue = attr.value;
|
|
173355
174107
|
const lcName = transformCaseFunc(name);
|
|
173356
174108
|
const initValue = attrValue;
|
|
173357
174109
|
let value = name === 'value' ? initValue : stringTrim(initValue);
|
|
@@ -173365,12 +174117,14 @@ function createDOMPurify() {
|
|
|
173365
174117
|
/* Full DOM Clobbering protection via namespace isolation,
|
|
173366
174118
|
* Prefix id and name attributes with `user-content-`
|
|
173367
174119
|
*/
|
|
173368
|
-
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
|
|
174120
|
+
if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name') && stringIndexOf(value, SANITIZE_NAMED_PROPS_PREFIX) !== 0) {
|
|
173369
174121
|
// Remove the attribute with this value
|
|
173370
174122
|
_removeAttribute(name, currentNode);
|
|
173371
174123
|
// Prefix the value and later re-create the attribute with the sanitized value
|
|
173372
174124
|
value = SANITIZE_NAMED_PROPS_PREFIX + value;
|
|
173373
174125
|
}
|
|
174126
|
+
// Else: already prefixed, leave the attribute alone — the prefix is
|
|
174127
|
+
// itself the clobbering protection, and re-applying it is incorrect.
|
|
173374
174128
|
/* Work around a security issue with comments inside attributes */
|
|
173375
174129
|
if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
|
|
173376
174130
|
_removeAttribute(name, currentNode);
|
|
@@ -173381,7 +174135,7 @@ function createDOMPurify() {
|
|
|
173381
174135
|
_removeAttribute(name, currentNode);
|
|
173382
174136
|
continue;
|
|
173383
174137
|
}
|
|
173384
|
-
/* Did the hooks
|
|
174138
|
+
/* Did the hooks force-keep the attribute? */
|
|
173385
174139
|
if (hookEvent.forceKeepAttr) {
|
|
173386
174140
|
continue;
|
|
173387
174141
|
}
|
|
@@ -173391,56 +174145,24 @@ function createDOMPurify() {
|
|
|
173391
174145
|
continue;
|
|
173392
174146
|
}
|
|
173393
174147
|
/* Work around a security issue in jQuery 3.0 */
|
|
173394
|
-
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(
|
|
174148
|
+
if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value)) {
|
|
173395
174149
|
_removeAttribute(name, currentNode);
|
|
173396
174150
|
continue;
|
|
173397
174151
|
}
|
|
173398
174152
|
/* Sanitize attribute content to be template-safe */
|
|
173399
174153
|
if (SAFE_FOR_TEMPLATES) {
|
|
173400
|
-
|
|
173401
|
-
value = stringReplace(value, expr, ' ');
|
|
173402
|
-
});
|
|
174154
|
+
value = _stripTemplateExpressions(value);
|
|
173403
174155
|
}
|
|
173404
174156
|
/* Is `value` valid for this attribute? */
|
|
173405
|
-
const lcTag = transformCaseFunc(currentNode.nodeName);
|
|
173406
174157
|
if (!_isValidAttribute(lcTag, lcName, value)) {
|
|
173407
174158
|
_removeAttribute(name, currentNode);
|
|
173408
174159
|
continue;
|
|
173409
174160
|
}
|
|
173410
174161
|
/* 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
|
-
}
|
|
174162
|
+
value = _applyTrustedTypesToAttribute(lcTag, lcName, namespaceURI, value);
|
|
173427
174163
|
/* Handle invalid data-* attribute set by try-catching it */
|
|
173428
174164
|
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
|
-
}
|
|
174165
|
+
_setAttributeValue(currentNode, name, namespaceURI, value);
|
|
173444
174166
|
}
|
|
173445
174167
|
}
|
|
173446
174168
|
/* Execute a hook if present */
|
|
@@ -173463,14 +174185,133 @@ function createDOMPurify() {
|
|
|
173463
174185
|
_sanitizeElements(shadowNode);
|
|
173464
174186
|
/* Check attributes next */
|
|
173465
174187
|
_sanitizeAttributes(shadowNode);
|
|
173466
|
-
/* Deep shadow DOM detected
|
|
173467
|
-
|
|
174188
|
+
/* Deep shadow DOM detected.
|
|
174189
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): use nodeType against the
|
|
174190
|
+
DOCUMENT_FRAGMENT_NODE constant rather than instanceof, so we
|
|
174191
|
+
recurse into <template>.content from foreign realms too. */
|
|
174192
|
+
if (_isDocumentFragment(shadowNode.content)) {
|
|
173468
174193
|
_sanitizeShadowDOM2(shadowNode.content);
|
|
173469
174194
|
}
|
|
174195
|
+
/* An element iterated here may itself host an attached
|
|
174196
|
+
shadow root. The default NodeIterator does not enter shadow
|
|
174197
|
+
trees, so a shadow root nested inside template.content was
|
|
174198
|
+
previously reached by no walk at all (the pre-pass at
|
|
174199
|
+
_sanitizeAttachedShadowRoots descends via childNodes, which
|
|
174200
|
+
doesn't enter template.content; the template-content recursion
|
|
174201
|
+
above iterates the content but never inspected shadowRoot).
|
|
174202
|
+
Walk it explicitly. The nodeType guard avoids reading
|
|
174203
|
+
shadowRoot off text / comment / CDATA / PI nodes that the
|
|
174204
|
+
iterator also surfaces. */
|
|
174205
|
+
const shadowNodeType = getNodeType ? getNodeType(shadowNode) : shadowNode.nodeType;
|
|
174206
|
+
if (shadowNodeType === NODE_TYPE.element) {
|
|
174207
|
+
const innerSr = getShadowRoot(shadowNode);
|
|
174208
|
+
if (_isDocumentFragment(innerSr)) {
|
|
174209
|
+
_sanitizeAttachedShadowRoots(innerSr);
|
|
174210
|
+
_sanitizeShadowDOM2(innerSr);
|
|
174211
|
+
}
|
|
174212
|
+
}
|
|
173470
174213
|
}
|
|
173471
174214
|
/* Execute a hook if present */
|
|
173472
174215
|
_executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
|
|
173473
174216
|
};
|
|
174217
|
+
/**
|
|
174218
|
+
* _sanitizeAttachedShadowRoots
|
|
174219
|
+
*
|
|
174220
|
+
* Walks `root` and feeds every attached shadow root we encounter into
|
|
174221
|
+
* the existing _sanitizeShadowDOM pipeline. The default node iterator
|
|
174222
|
+
* does not descend into shadow trees, so nodes inside an attached
|
|
174223
|
+
* shadow root would otherwise be skipped entirely.
|
|
174224
|
+
*
|
|
174225
|
+
* Two real input paths put attached shadow roots in front of us:
|
|
174226
|
+
* 1. IN_PLACE on a DOM node that already has shadow roots attached.
|
|
174227
|
+
* 2. DOM-node input where importNode(dirty, true) deep-clones the
|
|
174228
|
+
* shadow root because it was created with `clonable: true`.
|
|
174229
|
+
*
|
|
174230
|
+
* This pass runs once, up front, so the main iteration loop (and the
|
|
174231
|
+
* existing _sanitizeShadowDOM template-content recursion) stay
|
|
174232
|
+
* untouched — string-input paths are not affected.
|
|
174233
|
+
*
|
|
174234
|
+
* @param root the subtree root to walk for attached shadow roots
|
|
174235
|
+
*/
|
|
174236
|
+
const _sanitizeAttachedShadowRoots = function _sanitizeAttachedShadowRoots(root) {
|
|
174237
|
+
/* Iterative (explicit stack) rather than per-child recursion. DOM APIs
|
|
174238
|
+
impose no depth cap, so an attacker-shaped tree (JSON/CRDT/editor data
|
|
174239
|
+
built straight into the DOM — the IN_PLACE surface) deeper than the JS
|
|
174240
|
+
call-stack budget would otherwise overflow native recursion here and
|
|
174241
|
+
throw at the IN_PLACE entry pre-pass, before a single node is
|
|
174242
|
+
sanitized, leaving the caller's live tree untouched (fail-open). See
|
|
174243
|
+
campaign-3 F4. A heap stack keeps depth off the call stack.
|
|
174244
|
+
Each work item is either a node to descend into, or a deferred
|
|
174245
|
+
`_sanitizeShadowDOM` for an already-walked shadow root. The deferred
|
|
174246
|
+
form preserves the original post-order discipline: a shadow root's
|
|
174247
|
+
nested shadow roots are discovered before the outer shadow is
|
|
174248
|
+
sanitized (which may remove hosts). Pushes are in reverse of the
|
|
174249
|
+
desired processing order (LIFO): template content, then children, then
|
|
174250
|
+
the shadow-sanitize, then the shadow walk — so the order matches the
|
|
174251
|
+
previous recursion exactly. */
|
|
174252
|
+
const stack = [{
|
|
174253
|
+
node: root,
|
|
174254
|
+
shadow: null
|
|
174255
|
+
}];
|
|
174256
|
+
while (stack.length > 0) {
|
|
174257
|
+
const item = stack.pop();
|
|
174258
|
+
/* Deferred shadow-DOM sanitisation: runs after its subtree was walked. */
|
|
174259
|
+
if (item.shadow) {
|
|
174260
|
+
_sanitizeShadowDOM2(item.shadow);
|
|
174261
|
+
continue;
|
|
174262
|
+
}
|
|
174263
|
+
const node = item.node;
|
|
174264
|
+
const nodeType = getNodeType ? getNodeType(node) : node.nodeType;
|
|
174265
|
+
const isElement = nodeType === NODE_TYPE.element;
|
|
174266
|
+
/* (pushed last → processed first) Children, snapshotted in reverse so
|
|
174267
|
+
the first child is processed first. Snapshotting matters because a
|
|
174268
|
+
hook may detach siblings mid-walk. */
|
|
174269
|
+
const childNodes = getChildNodes(node);
|
|
174270
|
+
if (childNodes) {
|
|
174271
|
+
for (let i = childNodes.length - 1; i >= 0; --i) {
|
|
174272
|
+
stack.push({
|
|
174273
|
+
node: childNodes[i],
|
|
174274
|
+
shadow: null
|
|
174275
|
+
});
|
|
174276
|
+
}
|
|
174277
|
+
}
|
|
174278
|
+
/* (pushed before children → processed after them, matching the old
|
|
174279
|
+
"template content last" order) When the node is a <template>,
|
|
174280
|
+
descend into its content. */
|
|
174281
|
+
if (isElement) {
|
|
174282
|
+
const rootName = getNodeName ? getNodeName(node) : null;
|
|
174283
|
+
if (typeof rootName === 'string' && transformCaseFunc(rootName) === 'template') {
|
|
174284
|
+
const content = node.content;
|
|
174285
|
+
if (_isDocumentFragment(content)) {
|
|
174286
|
+
stack.push({
|
|
174287
|
+
node: content,
|
|
174288
|
+
shadow: null
|
|
174289
|
+
});
|
|
174290
|
+
}
|
|
174291
|
+
}
|
|
174292
|
+
}
|
|
174293
|
+
/* Shadow root (processed first): walk its subtree, then sanitise it.
|
|
174294
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
174295
|
+
rather than `instanceof DocumentFragment`, which is realm-bound and
|
|
174296
|
+
silently skipped foreign-realm shadow roots (e.g.
|
|
174297
|
+
iframe.contentDocument attachShadow). */
|
|
174298
|
+
if (isElement) {
|
|
174299
|
+
const sr = getShadowRoot(node);
|
|
174300
|
+
if (_isDocumentFragment(sr)) {
|
|
174301
|
+
/* Push the deferred sanitise first so it pops after the shadow
|
|
174302
|
+
walk we push next, i.e. nested shadow roots are discovered
|
|
174303
|
+
before this one is sanitised. */
|
|
174304
|
+
stack.push({
|
|
174305
|
+
node: null,
|
|
174306
|
+
shadow: sr
|
|
174307
|
+
}, {
|
|
174308
|
+
node: sr,
|
|
174309
|
+
shadow: null
|
|
174310
|
+
});
|
|
174311
|
+
}
|
|
174312
|
+
}
|
|
174313
|
+
}
|
|
174314
|
+
};
|
|
173474
174315
|
// eslint-disable-next-line complexity
|
|
173475
174316
|
DOMPurify.sanitize = function (dirty) {
|
|
173476
174317
|
let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
@@ -173487,13 +174328,9 @@ function createDOMPurify() {
|
|
|
173487
174328
|
}
|
|
173488
174329
|
/* Stringify, in case dirty is an object */
|
|
173489
174330
|
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');
|
|
174331
|
+
dirty = stringifyValue(dirty);
|
|
174332
|
+
if (typeof dirty !== 'string') {
|
|
174333
|
+
throw typeErrorCreate('dirty is not a string, aborting');
|
|
173497
174334
|
}
|
|
173498
174335
|
}
|
|
173499
174336
|
/* Return dirty HTML if DOMPurify cannot run */
|
|
@@ -173501,24 +174338,78 @@ function createDOMPurify() {
|
|
|
173501
174338
|
return dirty;
|
|
173502
174339
|
}
|
|
173503
174340
|
/* Assign config vars */
|
|
173504
|
-
if (
|
|
174341
|
+
if (SET_CONFIG) {
|
|
174342
|
+
/* Persistent setConfig() path: _parseConfig is skipped, so the sets are
|
|
174343
|
+
* not re-derived per call. Restore them from the pristine bindings
|
|
174344
|
+
* captured at setConfig() time so a previous call's hook clone (mutated
|
|
174345
|
+
* below) does not carry over. */
|
|
174346
|
+
ALLOWED_TAGS = SET_CONFIG_ALLOWED_TAGS;
|
|
174347
|
+
ALLOWED_ATTR = SET_CONFIG_ALLOWED_ATTR;
|
|
174348
|
+
} else {
|
|
173505
174349
|
_parseConfig(cfg);
|
|
173506
174350
|
}
|
|
174351
|
+
/* Clone the hook-mutable allowlists before the walk whenever an
|
|
174352
|
+
* uponSanitize* hook is registered. The hook event exposes ALLOWED_TAGS
|
|
174353
|
+
* and ALLOWED_ATTR by reference (as allowedTags / allowedAttributes), so
|
|
174354
|
+
* a hook that widens them would otherwise mutate the shared set
|
|
174355
|
+
* permanently: across later calls and across every element. Cloning per
|
|
174356
|
+
* walk keeps documented in-call widening working while scoping it to the
|
|
174357
|
+
* call. A single guard for both config paths - the per-call path rebinds
|
|
174358
|
+
* the sets in _parseConfig each call, the persistent path restores them
|
|
174359
|
+
* from the captured bindings just above - so the two cannot diverge. */
|
|
174360
|
+
if (hooks.uponSanitizeElement.length > 0 || hooks.uponSanitizeAttribute.length > 0) {
|
|
174361
|
+
ALLOWED_TAGS = clone(ALLOWED_TAGS);
|
|
174362
|
+
}
|
|
174363
|
+
if (hooks.uponSanitizeAttribute.length > 0) {
|
|
174364
|
+
ALLOWED_ATTR = clone(ALLOWED_ATTR);
|
|
174365
|
+
}
|
|
173507
174366
|
/* Clean up removed elements */
|
|
173508
174367
|
DOMPurify.removed = [];
|
|
173509
|
-
/*
|
|
173510
|
-
|
|
173511
|
-
|
|
173512
|
-
|
|
173513
|
-
|
|
173514
|
-
|
|
173515
|
-
|
|
173516
|
-
|
|
174368
|
+
/* Resolve IN_PLACE for this call without mutating persistent config.
|
|
174369
|
+
Writing the IN_PLACE closure variable here leaks under setConfig(),
|
|
174370
|
+
where _parseConfig is skipped on later calls: a single string call would
|
|
174371
|
+
disable in-place mode for every subsequent node call, returning a
|
|
174372
|
+
sanitized copy while leaving the caller's node — which in-place callers
|
|
174373
|
+
keep using and whose return value they ignore — unsanitized. REPORT-2. */
|
|
174374
|
+
const inPlace = IN_PLACE && typeof dirty !== 'string' && _isNode(dirty);
|
|
174375
|
+
if (inPlace) {
|
|
174376
|
+
/* Do some early pre-sanitization to avoid unsafe root nodes.
|
|
174377
|
+
Read nodeName through the cached prototype getter — a clobbering
|
|
174378
|
+
child named "nodeName" on the form root would otherwise shadow
|
|
174379
|
+
the property and let this check skip the root-allowlist
|
|
174380
|
+
validation entirely. */
|
|
174381
|
+
const nn = getNodeName ? getNodeName(dirty) : dirty.nodeName;
|
|
174382
|
+
if (typeof nn === 'string') {
|
|
174383
|
+
const tagName = transformCaseFunc(nn);
|
|
173517
174384
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
|
|
173518
174385
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
|
|
173519
174386
|
}
|
|
173520
174387
|
}
|
|
173521
|
-
|
|
174388
|
+
/* Pre-flight the root through _isClobbered. The iterator-driven
|
|
174389
|
+
removal path can not detach a parent-less root: _forceRemove
|
|
174390
|
+
falls through to Element.prototype.remove(), which per spec
|
|
174391
|
+
is a no-op on a node with no parent. A clobbered root would
|
|
174392
|
+
then survive the main loop with its attributes uninspected,
|
|
174393
|
+
because _sanitizeAttributes early-returns on _isClobbered. The
|
|
174394
|
+
result would be an attacker-controlled form, complete with any
|
|
174395
|
+
event-handler attributes the caller passed in, handed back to
|
|
174396
|
+
the application unsanitized. Refuse to sanitize such a root
|
|
174397
|
+
the same way we refuse a forbidden tag. GHSA-r47g-fvhr-h676. */
|
|
174398
|
+
if (_isClobbered(dirty)) {
|
|
174399
|
+
throw typeErrorCreate('root node is clobbered and cannot be sanitized in-place');
|
|
174400
|
+
}
|
|
174401
|
+
/* Sanitize attached shadow roots before the main iterator runs.
|
|
174402
|
+
The iterator does not descend into shadow trees. Same fail-closed
|
|
174403
|
+
barrier as the main walk (campaign-3 F2): a custom-element reaction
|
|
174404
|
+
inside a shadow root could abort this pre-pass before the walk runs,
|
|
174405
|
+
which would otherwise leave the entire live tree unsanitized. */
|
|
174406
|
+
try {
|
|
174407
|
+
_sanitizeAttachedShadowRoots(dirty);
|
|
174408
|
+
} catch (error) {
|
|
174409
|
+
_neutralizeRoot(dirty);
|
|
174410
|
+
throw error;
|
|
174411
|
+
}
|
|
174412
|
+
} else if (_isNode(dirty)) {
|
|
173522
174413
|
/* If dirty is a DOM element, append to an empty document to avoid
|
|
173523
174414
|
elements being stripped by the parser */
|
|
173524
174415
|
body = _initDocument('<!---->');
|
|
@@ -173532,12 +174423,18 @@ function createDOMPurify() {
|
|
|
173532
174423
|
// eslint-disable-next-line unicorn/prefer-dom-node-append
|
|
173533
174424
|
body.appendChild(importedNode);
|
|
173534
174425
|
}
|
|
174426
|
+
/* Clonable shadow roots are deep-cloned by importNode(); sanitize
|
|
174427
|
+
them before the main iterator runs, since the iterator does not
|
|
174428
|
+
descend into shadow trees. The walk routes every read through a
|
|
174429
|
+
cached prototype getter so clobbering descendants on a form root
|
|
174430
|
+
cannot hide a shadow host from this pass. */
|
|
174431
|
+
_sanitizeAttachedShadowRoots(importedNode);
|
|
173535
174432
|
} else {
|
|
173536
174433
|
/* Exit directly if we have nothing to do */
|
|
173537
174434
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
|
|
173538
174435
|
// eslint-disable-next-line unicorn/prefer-includes
|
|
173539
174436
|
dirty.indexOf('<') === -1) {
|
|
173540
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174437
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(dirty) : dirty;
|
|
173541
174438
|
}
|
|
173542
174439
|
/* Initialize the document to work on */
|
|
173543
174440
|
body = _initDocument(dirty);
|
|
@@ -173551,31 +174448,59 @@ function createDOMPurify() {
|
|
|
173551
174448
|
_forceRemove(body.firstChild);
|
|
173552
174449
|
}
|
|
173553
174450
|
/* Get node iterator */
|
|
173554
|
-
const nodeIterator = _createNodeIterator(
|
|
173555
|
-
/* Now start iterating over the created document
|
|
173556
|
-
|
|
173557
|
-
|
|
173558
|
-
|
|
173559
|
-
|
|
173560
|
-
|
|
173561
|
-
|
|
173562
|
-
|
|
173563
|
-
|
|
174451
|
+
const nodeIterator = _createNodeIterator(inPlace ? dirty : body);
|
|
174452
|
+
/* Now start iterating over the created document.
|
|
174453
|
+
The walk runs inside an exception barrier (campaign-3 F2): a re-entrant
|
|
174454
|
+
engine/custom-element mutation can detach a node mid-walk so
|
|
174455
|
+
`_forceRemove`'s parentless guard throws, aborting the loop. Without the
|
|
174456
|
+
barrier the caller's in-place tree would be left half-sanitized with the
|
|
174457
|
+
unvisited tail still armed. On any throw we fail closed — strip the
|
|
174458
|
+
in-place root bare — then rethrow so the existing throw contract is
|
|
174459
|
+
preserved. (String/DOM-copy paths never return the partial body, so the
|
|
174460
|
+
propagating throw is already fail-closed there.) */
|
|
174461
|
+
try {
|
|
174462
|
+
while (currentNode = nodeIterator.nextNode()) {
|
|
174463
|
+
/* Sanitize tags and elements */
|
|
174464
|
+
_sanitizeElements(currentNode);
|
|
174465
|
+
/* Check attributes next */
|
|
174466
|
+
_sanitizeAttributes(currentNode);
|
|
174467
|
+
/* Shadow DOM detected, sanitize it.
|
|
174468
|
+
Realm-safe check (GHSA-hpcv-96wg-7vj8): nodeType-based detection
|
|
174469
|
+
instead of instanceof, so foreign-realm <template>.content is
|
|
174470
|
+
walked correctly. */
|
|
174471
|
+
if (_isDocumentFragment(currentNode.content)) {
|
|
174472
|
+
_sanitizeShadowDOM2(currentNode.content);
|
|
174473
|
+
}
|
|
174474
|
+
}
|
|
174475
|
+
} catch (error) {
|
|
174476
|
+
if (inPlace) {
|
|
174477
|
+
_neutralizeRoot(dirty);
|
|
173564
174478
|
}
|
|
174479
|
+
throw error;
|
|
173565
174480
|
}
|
|
173566
174481
|
/* If we sanitized `dirty` in-place, return it. */
|
|
173567
|
-
if (
|
|
174482
|
+
if (inPlace) {
|
|
174483
|
+
/* Fail-closed completion of the audit-5 F1 fix: every node removed from
|
|
174484
|
+
the caller's live tree is detached but may still hold a queued
|
|
174485
|
+
resource-event handler that fires in page scope after we return. The
|
|
174486
|
+
move-hoist covers only disallowed-tag KEEP_CONTENT removals; strip the
|
|
174487
|
+
non-allow-listed attributes off every other removed subtree (clobber,
|
|
174488
|
+
mXSS, namespace, comments, KEEP_CONTENT:false, …) so those handlers are
|
|
174489
|
+
cancelled before any event can fire. Runs synchronously, pre-return. */
|
|
174490
|
+
arrayForEach(DOMPurify.removed, entry => {
|
|
174491
|
+
if (entry.element) {
|
|
174492
|
+
_neutralizeSubtree(entry.element);
|
|
174493
|
+
}
|
|
174494
|
+
});
|
|
174495
|
+
if (SAFE_FOR_TEMPLATES) {
|
|
174496
|
+
_scrubTemplateExpressions2(dirty);
|
|
174497
|
+
}
|
|
173568
174498
|
return dirty;
|
|
173569
174499
|
}
|
|
173570
174500
|
/* Return sanitized string or DOM */
|
|
173571
174501
|
if (RETURN_DOM) {
|
|
173572
174502
|
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;
|
|
174503
|
+
_scrubTemplateExpressions2(body);
|
|
173579
174504
|
}
|
|
173580
174505
|
if (RETURN_DOM_FRAGMENT) {
|
|
173581
174506
|
returnNode = createDocumentFragment.call(body.ownerDocument);
|
|
@@ -173605,20 +174530,28 @@ function createDOMPurify() {
|
|
|
173605
174530
|
}
|
|
173606
174531
|
/* Sanitize final string template-safe */
|
|
173607
174532
|
if (SAFE_FOR_TEMPLATES) {
|
|
173608
|
-
|
|
173609
|
-
serializedHTML = stringReplace(serializedHTML, expr, ' ');
|
|
173610
|
-
});
|
|
174533
|
+
serializedHTML = _stripTemplateExpressions(serializedHTML);
|
|
173611
174534
|
}
|
|
173612
|
-
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ?
|
|
174535
|
+
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? _createTrustedHTML(serializedHTML) : serializedHTML;
|
|
173613
174536
|
};
|
|
173614
174537
|
DOMPurify.setConfig = function () {
|
|
173615
174538
|
let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
|
|
173616
174539
|
_parseConfig(cfg);
|
|
173617
174540
|
SET_CONFIG = true;
|
|
174541
|
+
SET_CONFIG_ALLOWED_TAGS = ALLOWED_TAGS;
|
|
174542
|
+
SET_CONFIG_ALLOWED_ATTR = ALLOWED_ATTR;
|
|
173618
174543
|
};
|
|
173619
174544
|
DOMPurify.clearConfig = function () {
|
|
173620
174545
|
CONFIG = null;
|
|
173621
174546
|
SET_CONFIG = false;
|
|
174547
|
+
SET_CONFIG_ALLOWED_TAGS = null;
|
|
174548
|
+
SET_CONFIG_ALLOWED_ATTR = null;
|
|
174549
|
+
// Drop any caller-supplied Trusted Types policy so it cannot poison later
|
|
174550
|
+
// `RETURN_TRUSTED_TYPE` output. The internal default policy (cached, and
|
|
174551
|
+
// never recreated — Trusted Types throws on duplicate names) is restored by
|
|
174552
|
+
// the next `_parseConfig`. See GHSA-vxr8-fq34-vvx9.
|
|
174553
|
+
trustedTypesPolicy = defaultTrustedTypesPolicy;
|
|
174554
|
+
emptyHTML = '';
|
|
173622
174555
|
};
|
|
173623
174556
|
DOMPurify.isValidAttribute = function (tag, attr, value) {
|
|
173624
174557
|
/* Initialize shared config vars if necessary. */
|
|
@@ -173633,9 +174566,19 @@ function createDOMPurify() {
|
|
|
173633
174566
|
if (typeof hookFunction !== 'function') {
|
|
173634
174567
|
return;
|
|
173635
174568
|
}
|
|
174569
|
+
/* Reject unknown entry points. Without this, a non-hook key (e.g.
|
|
174570
|
+
* '__proto__') indexes off the prototype chain rather than a real
|
|
174571
|
+
* hook array, and arrayPush then writes to Object.prototype. Guard
|
|
174572
|
+
* with an own-property check against the known hook names. */
|
|
174573
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174574
|
+
return;
|
|
174575
|
+
}
|
|
173636
174576
|
arrayPush(hooks[entryPoint], hookFunction);
|
|
173637
174577
|
};
|
|
173638
174578
|
DOMPurify.removeHook = function (entryPoint, hookFunction) {
|
|
174579
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174580
|
+
return undefined;
|
|
174581
|
+
}
|
|
173639
174582
|
if (hookFunction !== undefined) {
|
|
173640
174583
|
const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
|
|
173641
174584
|
return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
|
|
@@ -173643,6 +174586,9 @@ function createDOMPurify() {
|
|
|
173643
174586
|
return arrayPop(hooks[entryPoint]);
|
|
173644
174587
|
};
|
|
173645
174588
|
DOMPurify.removeHooks = function (entryPoint) {
|
|
174589
|
+
if (!objectHasOwnProperty(hooks, entryPoint)) {
|
|
174590
|
+
return;
|
|
174591
|
+
}
|
|
173646
174592
|
hooks[entryPoint] = [];
|
|
173647
174593
|
};
|
|
173648
174594
|
DOMPurify.removeAllHooks = function () {
|