From 0965077cac9d88afd09e595d661f4b104efaa590 Mon Sep 17 00:00:00 2001 From: Alvaro Parker Date: Mon, 5 Oct 2026 12:04:11 -0300 Subject: [PATCH] feat(ui): render assistant replies as Markdown --- s2s-client/.gitignore | 4 +- s2s-client/README.md | 14 + s2s-client/package-lock.json | 42 + s2s-client/package.json | 12 + s2s-client/scripts/vendor-dependencies.mjs | 18 + s2s-client/style.css | 97 + s2s-client/ui/chat.js | 22 +- s2s-client/ui/markdown.js | 56 + s2s-client/vendor/dompurify/LICENSE | 202 ++ s2s-client/vendor/dompurify/LICENSE-MPL | 373 +++ s2s-client/vendor/dompurify/purify.es.mjs | 2464 +++++++++++++++++ s2s-client/vendor/dompurify/purify.es.mjs.map | 1 + s2s-client/vendor/marked/LICENSE | 44 + s2s-client/vendor/marked/marked.esm.js | 80 + s2s-client/vendor/marked/marked.esm.js.map | 7 + 15 files changed, 3430 insertions(+), 6 deletions(-) create mode 100644 s2s-client/package-lock.json create mode 100644 s2s-client/package.json create mode 100644 s2s-client/scripts/vendor-dependencies.mjs create mode 100644 s2s-client/ui/markdown.js create mode 100644 s2s-client/vendor/dompurify/LICENSE create mode 100644 s2s-client/vendor/dompurify/LICENSE-MPL create mode 100644 s2s-client/vendor/dompurify/purify.es.mjs create mode 100644 s2s-client/vendor/dompurify/purify.es.mjs.map create mode 100644 s2s-client/vendor/marked/LICENSE create mode 100644 s2s-client/vendor/marked/marked.esm.js create mode 100644 s2s-client/vendor/marked/marked.esm.js.map diff --git a/s2s-client/.gitignore b/s2s-client/.gitignore index f43f76b..8a9283c 100644 --- a/s2s-client/.gitignore +++ b/s2s-client/.gitignore @@ -14,8 +14,10 @@ __pycache__/ *.pyc .venv/ venv/ +node_modules/ +!package-lock.json # Local-only replica deploy script (never tracked) deploy_replica.py -.env \ No newline at end of file +.env diff --git a/s2s-client/README.md b/s2s-client/README.md index 4abd611..167b3cc 100644 --- a/s2s-client/README.md +++ b/s2s-client/README.md @@ -21,6 +21,20 @@ connection modes, Docker, settings, and usage limits. See the [documentation index](../docs/README.md), [architecture](../docs/client/architecture.md), and [change history](CHANGELOG.md) for implementation details and history. +Assistant replies render Markdown with Marked and DOMPurify. Their pinned, +checked-in browser modules are served from `vendor/`, including upstream +licenses, so the UI does not require a CDN or a Node.js runtime. To refresh the +browser dependencies with Node.js 20 or newer after updating `package.json`: + +```bash +cd s2s-client +npm install --ignore-scripts +npm run vendor +``` + +Commit `package-lock.json` and the generated `vendor/` files together. Existing +locked versions can be restored with `npm ci --ignore-scripts`. + Build from the repository root so the container includes the documentation: ```bash diff --git a/s2s-client/package-lock.json b/s2s-client/package-lock.json new file mode 100644 index 0000000..56110c3 --- /dev/null +++ b/s2s-client/package-lock.json @@ -0,0 +1,42 @@ +{ + "name": "vocero-ui", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vocero-ui", + "dependencies": { + "dompurify": "3.4.16", + "marked": "18.0.14" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/dompurify": { + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.16.tgz", + "integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/marked": { + "version": "18.0.14", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.0.14.tgz", + "integrity": "sha512-mBHK6FBHuBAlhgRe88w9F0O1AbwwXJUcQibUbC/QcdTbVGAD7aWza+xt3N6oT/jCZx3/OMeS+8rnuiHZcQ9s7A==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + } + } +} diff --git a/s2s-client/package.json b/s2s-client/package.json new file mode 100644 index 0000000..ce8eb2b --- /dev/null +++ b/s2s-client/package.json @@ -0,0 +1,12 @@ +{ + "name": "vocero-ui", + "private": true, + "type": "module", + "scripts": { + "vendor": "node scripts/vendor-dependencies.mjs" + }, + "dependencies": { + "dompurify": "3.4.16", + "marked": "18.0.14" + } +} diff --git a/s2s-client/scripts/vendor-dependencies.mjs b/s2s-client/scripts/vendor-dependencies.mjs new file mode 100644 index 0000000..184c0b2 --- /dev/null +++ b/s2s-client/scripts/vendor-dependencies.mjs @@ -0,0 +1,18 @@ +import { copyFile, mkdir } from "node:fs/promises"; + +const root = new URL("../", import.meta.url); +const assets = [ + ["marked/lib/marked.esm.js", "marked/marked.esm.js"], + ["marked/lib/marked.esm.js.map", "marked/marked.esm.js.map"], + ["marked/LICENSE", "marked/LICENSE"], + ["dompurify/dist/purify.es.mjs", "dompurify/purify.es.mjs"], + ["dompurify/dist/purify.es.mjs.map", "dompurify/purify.es.mjs.map"], + ["dompurify/LICENSE", "dompurify/LICENSE"], + ["dompurify/LICENSE-MPL", "dompurify/LICENSE-MPL"], +]; + +for (const [source, destination] of assets) { + const target = new URL(`vendor/${destination}`, root); + await mkdir(new URL("./", target), { recursive: true }); + await copyFile(new URL(`node_modules/${source}`, root), target); +} diff --git a/s2s-client/style.css b/s2s-client/style.css index 30b42dc..75fc9af 100644 --- a/s2s-client/style.css +++ b/s2s-client/style.css @@ -2772,6 +2772,103 @@ button[data-reports-sidebar-toggle] { } .hist-msg.user .hist-body.partial { opacity: 0.68; } +/* Markdown is confined to assistant message bodies in both themes. */ +.markdown-content { + min-width: 0; + max-width: 100%; + box-sizing: border-box; + white-space: normal !important; + overflow-wrap: anywhere; +} +.markdown-content > :first-child { margin-top: 0; } +.markdown-content > :last-child { margin-bottom: 0; } +.markdown-content p { margin: 0 0 0.75em; } +.markdown-content strong { font-weight: 700; } +.markdown-content :is(h1, h2, h3, h4, h5, h6) { + margin: 1em 0 0.5em; + font-size: 1.12em; + font-weight: 700; + line-height: 1.35; +} +.markdown-content :is(ul, ol) { + margin: 0.65em 0; + padding-inline-start: 1.5em; +} +.markdown-content ul { list-style-type: disc; } +.markdown-content ol { list-style-type: decimal; } +.markdown-content li + li { margin-top: 0.35em; } +.markdown-content li > p { margin: 0.25em 0; } +.markdown-content li :is(ul, ol) { margin: 0.3em 0; } +.markdown-content a { + color: var(--brand-3); + text-decoration: underline; + text-underline-offset: 0.15em; +} +html[data-theme="white"] .markdown-content a { color: #1d4ed8; } +.markdown-content :is(a, pre, .markdown-table):focus-visible { + outline: 2px solid var(--brand); + outline-offset: 3px; +} +.markdown-content blockquote { + margin: 0.75em 0; + padding-inline-start: 12px; + border-inline-start: 3px solid var(--brand); + color: var(--text-dim); +} +.markdown-content code { + padding: 0.15em 0.35em; + border-radius: 4px; + background: color-mix(in srgb, currentColor 8%, transparent); + font-family: var(--font-mono); + font-size: 0.9em; +} +.markdown-content pre { + max-width: 100%; + margin: 0.75em 0; + padding: 12px; + border: 1px solid var(--border-strong); + border-radius: 8px; + overflow: auto; + white-space: pre; +} +.markdown-content pre code { + display: block; + padding: 0; + background: transparent; + white-space: pre; + overflow-wrap: normal; + word-break: normal; +} +.markdown-content hr { + margin: 1em 0; + border: 0; + border-top: 1px solid var(--border-strong); +} +.markdown-content img { + max-width: 100%; + height: auto; +} +.markdown-content .markdown-table { + max-width: 100%; + margin: 0.75em 0; + overflow-x: auto; + overscroll-behavior-x: contain; +} +.markdown-content table { + width: 100%; + border-collapse: collapse; + font-size: 0.92em; +} +.markdown-content :is(th, td) { + padding: 7px 10px; + border: 1px solid var(--border-strong); + text-align: start; + vertical-align: top; +} +.markdown-content th { font-weight: 700; } +.markdown-content [align="center"] { text-align: center; } +.markdown-content [align="right"] { text-align: end; } + /* ─── Tool call history item ─────────────────────────────────────────── */ .hist-tool-header { diff --git a/s2s-client/ui/chat.js b/s2s-client/ui/chat.js index c1e19ff..6f44a49 100644 --- a/s2s-client/ui/chat.js +++ b/s2s-client/ui/chat.js @@ -18,6 +18,7 @@ */ import { $, escHtml, DEBUG } from "./dom.js"; +import { renderMarkdown } from "./markdown.js"; /** * @param {string} hypothesisId @@ -99,6 +100,8 @@ export class ChatView { // ── Assistant transcript state (keyed by response_id) ────────────────── /** @type {Map} */ this._asstByResp = new Map(); + /** Original transcript text is retained independently of rendered markup. @type {WeakMap} */ + this._messageText = new WeakMap(); // ── Ephemeral bubble auto-dismiss ────────────────────────────────────── // Per-element expiry (epoch ms). A bubble fades once its expiry passes — @@ -310,9 +313,10 @@ export class ChatView { el.setAttribute("data-asst-name", this._assistantName); el.setAttribute("data-timestamp", stamp); } - el.innerHTML = `
${label}
${escHtml(text)}
`; + el.innerHTML = `
${label}
`; const body = /** @type {HTMLDivElement} */ (el.lastElementChild); if (body) { + this._renderMessageBody(body, text, role === "assistant"); if (role === "user") { body.setAttribute("data-user-name", el.getAttribute("data-user-name") || ""); } else { @@ -323,6 +327,14 @@ export class ChatView { return el; } + /** @param {HTMLElement} body @param {string} text @param {boolean} assistant */ + _renderMessageBody(body, text, assistant) { + this._messageText.set(body, text); + body.classList.toggle("markdown-content", assistant); + if (assistant) body.replaceChildren(renderMarkdown(text)); + else body.textContent = text; + } + // ── Ephemeral bubbles ─────────────────────────────────────────────────── /** @param {"user"|"assistant"|"tool"} role @param {string} text @returns {HTMLElement} */ @@ -348,8 +360,8 @@ export class ChatView { /** @param {HTMLElement} el @param {string} text */ _updateBubbleText(el, text) { - const t = el.querySelector(".bubble-body"); - if (t) t.textContent = text; + const t = /** @type {HTMLElement | null} */ (el.querySelector(".bubble-body")); + if (t) this._renderMessageBody(t, text, el.classList.contains("assistant")); } /** @param {HTMLElement} el */ @@ -433,7 +445,7 @@ export class ChatView { if (!el) return; const body = /** @type {HTMLElement | null} */ (el.querySelector(".hist-body")); if (!body) return; - body.textContent = text; + this._renderMessageBody(body, text, el.classList.contains("assistant")); body.classList.toggle("partial", partial); this._scrollToBottom(); } @@ -544,7 +556,7 @@ export class ChatView { const role = row.classList.contains("user") ? "user" : "assistant"; const out = /** @type {{ type: string; text: string; timestamp?: string; name?: string; initials?: string; interrupted?: boolean }} */ ({ type: role, - text: body.textContent || "", + text: this._messageText.get(body) ?? body.textContent ?? "", }); if (ts) out.timestamp = ts; if (role === "user") { diff --git a/s2s-client/ui/markdown.js b/s2s-client/ui/markdown.js new file mode 100644 index 0000000..7d320ac --- /dev/null +++ b/s2s-client/ui/markdown.js @@ -0,0 +1,56 @@ +// @ts-check +import { Marked } from "../vendor/marked/marked.esm.js"; +import DOMPurify from "../vendor/dompurify/purify.es.mjs"; +import { escHtml } from "./dom.js"; + +const markdown = new Marked({ + gfm: true, + breaks: true, + renderer: { + // Treat embedded HTML as text. Replies can format Markdown, not UI controls. + html: ({ text }) => escHtml(text), + checkbox: ({ checked }) => checked ? "☑ " : "☐ ", + }, +}); + +/** Parse assistant text into passive, sanitized message content. @param {string} text */ +export function renderMarkdown(text) { + if (!DOMPurify.isSupported) return document.createTextNode(text); + try { + const html = markdown.parse(text, { async: false }); + const fragment = DOMPurify.sanitize(html, { + ALLOWED_TAGS: [ + "p", "br", "strong", "em", "del", "a", "img", "ul", "ol", "li", + "h1", "h2", "h3", "h4", "h5", "h6", "blockquote", "pre", "code", "hr", + "table", "thead", "tbody", "tr", "th", "td", + ], + ALLOWED_ATTR: ["href", "title", "src", "alt", "start", "align"], + ALLOW_DATA_ATTR: false, + ALLOW_ARIA_ATTR: false, + RETURN_DOM_FRAGMENT: true, + }); + for (const link of fragment.querySelectorAll("a")) { + if (!link.hasAttribute("href")) { + link.replaceWith(...link.childNodes); + } else { + link.target = "_blank"; + link.rel = "noopener noreferrer"; + } + } + for (const image of fragment.querySelectorAll("img")) image.loading = "lazy"; + for (const table of fragment.querySelectorAll("table")) { + const wrapper = document.createElement("div"); + wrapper.className = "markdown-table"; + wrapper.tabIndex = 0; + wrapper.setAttribute("role", "region"); + wrapper.setAttribute("aria-label", "Tabla de la respuesta"); + table.replaceWith(wrapper); + wrapper.appendChild(table); + } + for (const code of fragment.querySelectorAll("pre")) code.tabIndex = 0; + return fragment; + } catch (err) { + console.warn("[ui] Markdown rendering failed", err); + return document.createTextNode(text); + } +} diff --git a/s2s-client/vendor/dompurify/LICENSE b/s2s-client/vendor/dompurify/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/s2s-client/vendor/dompurify/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/s2s-client/vendor/dompurify/LICENSE-MPL b/s2s-client/vendor/dompurify/LICENSE-MPL new file mode 100644 index 0000000..14e2f77 --- /dev/null +++ b/s2s-client/vendor/dompurify/LICENSE-MPL @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/s2s-client/vendor/dompurify/purify.es.mjs b/s2s-client/vendor/dompurify/purify.es.mjs new file mode 100644 index 0000000..5b666ff --- /dev/null +++ b/s2s-client/vendor/dompurify/purify.es.mjs @@ -0,0 +1,2464 @@ +/*! @license DOMPurify 3.4.16 | (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.16/LICENSE */ +function _OverloadYield(e, d) { + this.v = e, this.k = d; +} +function _arrayLikeToArray(r, a) { + (null == a || a > r.length) && (a = r.length); + for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; + return n; +} +function _arrayWithHoles(r) { + if (Array.isArray(r)) return r; +} +function _iterableToArrayLimit(r, l) { + var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; + if (null != t) { + var e, n, i, u, a = [], f = !0, o = !1; + try { + if (i = (t = t.call(r)).next, 0 === l) { + if (Object(t) !== t) return; + f = !1; + } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); + } catch (r) { + o = !0, n = r; + } finally { + try { + if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return; + } finally { + if (o) throw n; + } + } + return a; + } +} +function _nonIterableRest() { + throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); +} +/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ +function _slicedToArray(r, e) { + return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); +} +function _unsupportedIterableToArray(r, a) { + if (r) { + if ("string" == typeof r) return _arrayLikeToArray(r, a); + var t = {}.toString.call(r).slice(8, -1); + 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; + } +} +function AsyncGenerator(e) { + var t, n; + function resume(t, n) { + try { + var r = e[t](n), o = r.value, u = o instanceof _OverloadYield; + Promise.resolve(u ? o.v : o).then(function(n) { + if (u) { + var i = "return" === t && o.k ? t : "next"; + if (!o.k || n.done) return resume(i, n); + n = e[i](n).value; + } + settle(!!r.done, n); + }, function(e) { + resume("throw", e); + }); + } catch (e) { + settle(2, e); + } + } + function settle(e, r) { + 2 === e ? t.reject(r) : t.resolve({ + value: r, + done: e + }), (t = t.next) ? resume(t.key, t.arg) : n = null; + } + this._invoke = function(e, r) { + return new Promise(function(o, u) { + var i = { + key: e, + arg: r, + resolve: o, + reject: u, + next: null + }; + n ? n = n.next = i : (t = n = i, resume(e, r)); + }); + }, "function" != typeof e.return && (this.return = void 0); +} +AsyncGenerator.prototype["function" == typeof Symbol && Symbol.asyncIterator || "@@asyncIterator"] = function() { + return this; +}, AsyncGenerator.prototype.next = function(e) { + return this._invoke("next", e); +}, AsyncGenerator.prototype.throw = function(e) { + return this._invoke("throw", e); +}, AsyncGenerator.prototype.return = function(e) { + return this._invoke("return", e); +}; +const entries = Object.entries; +const setPrototypeOf = Object.setPrototypeOf; +const isFrozen = Object.isFrozen; +const getPrototypeOf = Object.getPrototypeOf; +const getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; +let freeze = Object.freeze; +let seal = Object.seal; +let create = Object.create; +let _ref = typeof Reflect !== "undefined" && Reflect; +let apply = _ref.apply; +let construct = _ref.construct; +if (!freeze) freeze = function freeze(x) { + return x; +}; +if (!seal) seal = function seal(x) { + return x; +}; +if (!apply) apply = function apply(func, thisArg) { + for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) args[_key - 2] = arguments[_key]; + return func.apply(thisArg, args); +}; +if (!construct) construct = function construct(Func) { + for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) args[_key2 - 1] = arguments[_key2]; + return new Func(...args); +}; +const arrayForEach = unapply(Array.prototype.forEach); +Array.prototype.indexOf; +const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf); +const arrayPop = unapply(Array.prototype.pop); +const arrayPush = unapply(Array.prototype.push); +Array.prototype.slice; +const arraySplice = unapply(Array.prototype.splice); +const arrayIsArray = Array.isArray; +const stringToLowerCase = unapply(String.prototype.toLowerCase); +const stringToString = unapply(String.prototype.toString); +const stringMatch = unapply(String.prototype.match); +const stringReplace = unapply(String.prototype.replace); +const stringIndexOf = unapply(String.prototype.indexOf); +const stringTrim = unapply(String.prototype.trim); +const numberToString = unapply(Number.prototype.toString); +const booleanToString = unapply(Boolean.prototype.toString); +const bigintToString = typeof BigInt === "undefined" ? null : unapply(BigInt.prototype.toString); +const symbolToString = typeof Symbol === "undefined" ? null : unapply(Symbol.prototype.toString); +const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty); +const objectToString = unapply(Object.prototype.toString); +const regExpTest = unapply(RegExp.prototype.test); +const typeErrorCreate = unconstruct(TypeError); +/** +* Creates a new function that calls the given function with a specified thisArg and arguments. +* +* @param func - The function to be wrapped and called. +* @returns A new function that calls the given function with a specified thisArg and arguments. +*/ +function unapply(func) { + return function(thisArg) { + if (thisArg instanceof RegExp) thisArg.lastIndex = 0; + for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) args[_key3 - 1] = arguments[_key3]; + return apply(func, thisArg, args); + }; +} +/** +* Creates a new function that constructs an instance of the given constructor function with the provided arguments. +* +* @param func - The constructor function to be wrapped and called. +* @returns A new function that constructs an instance of the given constructor function with the provided arguments. +*/ +function unconstruct(Func) { + return function() { + for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) args[_key4] = arguments[_key4]; + return construct(Func, args); + }; +} +/** +* Add properties to a lookup table +* +* @param set - The set to which elements will be added. +* @param array - The array containing elements to be added to the set. +* @param transformCaseFunc - An optional function to transform the case of each element before adding to the set. +* @returns The modified set with added elements. +*/ +function addToSet(set, array) { + let transformCaseFunc = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : stringToLowerCase; + if (setPrototypeOf) setPrototypeOf(set, null); + if (!arrayIsArray(array)) return set; + let l = array.length; + while (l--) { + let element = array[l]; + if (typeof element === "string") { + const lcElement = transformCaseFunc(element); + if (lcElement !== element) { + if (!isFrozen(array)) array[l] = lcElement; + element = lcElement; + } + } + set[element] = true; + } + return set; +} +/** +* Clean up an array to harden against CSPP +* +* @param array - The array to be cleaned. +* @returns The cleaned version of the array +*/ +function cleanArray(array) { + for (let index = 0; index < array.length; index++) if (!objectHasOwnProperty(array, index)) array[index] = null; + return array; +} +/** +* Shallow clone an object +* +* @param object - The object to be cloned. +* @returns A new object that copies the original. +*/ +function clone(object) { + const newObject = create(null); + for (const _ref2 of entries(object)) { + var _ref3 = _slicedToArray(_ref2, 2); + const property = _ref3[0]; + const value = _ref3[1]; + if (objectHasOwnProperty(object, property)) { + if (arrayIsArray(value)) newObject[property] = cleanArray(value); + else if (value && typeof value === "object" && value.constructor === Object) newObject[property] = clone(value); + else newObject[property] = value; + } + } + return newObject; +} +/** +* Convert non-node values into strings without depending on direct property access. +* +* @param value - The value to stringify. +* @returns A string representation of the provided value. +*/ +function stringifyValue(value) { + switch (typeof value) { + case "string": return value; + case "number": return numberToString(value); + case "boolean": return booleanToString(value); + case "bigint": return bigintToString ? bigintToString(value) : "0"; + case "symbol": return symbolToString ? symbolToString(value) : "Symbol()"; + case "undefined": return objectToString(value); + case "function": + case "object": { + if (value === null) return objectToString(value); + const valueAsRecord = value; + const valueToString = lookupGetter(valueAsRecord, "toString"); + if (typeof valueToString === "function") { + const stringified = valueToString(valueAsRecord); + return typeof stringified === "string" ? stringified : objectToString(stringified); + } + return objectToString(value); + } + default: return objectToString(value); + } +} +/** +* This method automatically checks if the prop is function or getter and behaves accordingly. +* +* @param object - The object to look up the getter function in its prototype chain. +* @param prop - The property name for which to find the getter function. +* @returns The getter function found in the prototype chain or a fallback function. +*/ +function lookupGetter(object, prop) { + while (object !== null) { + const desc = getOwnPropertyDescriptor(object, prop); + if (desc) { + if (desc.get) return unapply(desc.get); + if (typeof desc.value === "function") return unapply(desc.value); + } + object = getPrototypeOf(object); + } + function fallbackValue() { + return null; + } + return fallbackValue; +} +function isRegex(value) { + try { + regExpTest(value, ""); + return true; + } catch (_unused) { + return false; + } +} +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" +]); +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" +]); +const svgFilters = freeze([ + "feBlend", + "feColorMatrix", + "feComponentTransfer", + "feComposite", + "feConvolveMatrix", + "feDiffuseLighting", + "feDisplacementMap", + "feDistantLight", + "feDropShadow", + "feFlood", + "feFuncA", + "feFuncB", + "feFuncG", + "feFuncR", + "feGaussianBlur", + "feImage", + "feMerge", + "feMergeNode", + "feMorphology", + "feOffset", + "fePointLight", + "feSpecularLighting", + "feSpotLight", + "feTile", + "feTurbulence" +]); +const svgDisallowed = freeze([ + "animate", + "color-profile", + "cursor", + "discard", + "font-face", + "font-face-format", + "font-face-name", + "font-face-src", + "font-face-uri", + "foreignobject", + "hatch", + "hatchpath", + "mesh", + "meshgradient", + "meshpatch", + "meshrow", + "missing-glyph", + "script", + "set", + "solidcolor", + "unknown", + "use" +]); +const mathMl$1 = freeze([ + "math", + "menclose", + "merror", + "mfenced", + "mfrac", + "mglyph", + "mi", + "mlabeledtr", + "mmultiscripts", + "mn", + "mo", + "mover", + "mpadded", + "mphantom", + "mroot", + "mrow", + "ms", + "mspace", + "msqrt", + "mstyle", + "msub", + "msup", + "msubsup", + "mtable", + "mtd", + "mtext", + "mtr", + "munder", + "munderover", + "mprescripts" +]); +const mathMlDisallowed = freeze([ + "maction", + "maligngroup", + "malignmark", + "mlongdiv", + "mscarries", + "mscarry", + "msgroup", + "mstack", + "msline", + "msrow", + "semantics", + "annotation", + "annotation-xml", + "mprescripts", + "none" +]); +const text = freeze(["#text"]); +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" +]); +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", + "dominant-baseline", + "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", + "pointer-events", + "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-orientation", + "text-rendering", + "textlength", + "type", + "u1", + "u2", + "unicode", + "values", + "vector-effect", + "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" +]); +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" +]); +const xml = freeze([ + "xlink:href", + "xml:id", + "xlink:title", + "xml:space", + "xmlns:xlink" +]); +const MUSTACHE_EXPR = seal(/{{[\w\W]*|^[\w\W]*}}/g); +const ERB_EXPR = seal(/<%[\w\W]*|^[\w\W]*%>/g); +const TMPLIT_EXPR = seal(/\${[\w\W]*/g); +const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); +const ARIA_ATTR = seal(/^aria-[\-\w]+$/); +const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i); +const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); +const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g); +const DOCTYPE_NAME = seal(/^html$/i); +const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i); +const ELEMENT_MARKUP_PROBE = seal(/<[/\w!]/g); +const COMMENT_MARKUP_PROBE = seal(/<[/\w]/g); +const FALLBACK_TAG_CLOSE = seal(/<\/no(script|embed|frames)/i); +const SELF_CLOSING_TAG = seal(/\/>/i); +const NODE_TYPE = { + element: 1, + attribute: 2, + text: 3, + cdataSection: 4, + entityReference: 5, + entityNode: 6, + processingInstruction: 7, + comment: 8, + document: 9, + documentType: 10, + documentFragment: 11, + notation: 12 +}; +const LITERAL_TEXT_ELEMENT_NAMES = [ + "style", + "script", + "xmp", + "iframe", + "noembed", + "noframes", + "plaintext", + "noscript" +]; +const LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES)); +const LITERAL_TEXT_CLOSE = function() { + const map = {}; + arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => { + map[name] = seal(new RegExp("])", "i")); + }); + return freeze(map); +}(); +const getGlobal = function getGlobal() { + return typeof window === "undefined" ? null : window; +}; +/** +* Creates a no-op policy for internal use only. +* Don't export this function outside this module! +* @param trustedTypes The policy factory. +* @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix). +* @return The policy created (or null, if Trusted Types +* are not supported or creating the policy failed). +*/ +const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) { + if (typeof trustedTypes !== "object" || typeof trustedTypes.createPolicy !== "function") return null; + let suffix = null; + const ATTR_NAME = "data-tt-policy-suffix"; + if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) suffix = purifyHostElement.getAttribute(ATTR_NAME); + const policyName = "dompurify" + (suffix ? "#" + suffix : ""); + try { + return trustedTypes.createPolicy(policyName, { + createHTML(html) { + return html; + }, + createScriptURL(scriptUrl) { + return scriptUrl; + } + }); + } catch (_) { + console.warn("TrustedTypes policy " + policyName + " could not be created."); + return null; + } +}; +const _createHooksMap = function _createHooksMap() { + return { + afterSanitizeAttributes: [], + afterSanitizeElements: [], + afterSanitizeShadowDOM: [], + beforeSanitizeAttributes: [], + beforeSanitizeElements: [], + beforeSanitizeShadowDOM: [], + uponSanitizeAttribute: [], + uponSanitizeElement: [], + uponSanitizeShadowNode: [] + }; +}; +/** +* Resolve a set-valued configuration option: a fresh set built from +* cfg[key] when it is an own array property (seeded with a clone of +* options.base when given, case-normalized via options.transform), +* the fallback set otherwise. +* +* @param cfg the cloned, prototype-free configuration object +* @param key the configuration property to read +* @param fallback the set to use when the option is absent or not an array +* @param options transform and optional base set to merge into +* @returns the resolved set +*/ +const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) { + return objectHasOwnProperty(cfg, key) && arrayIsArray(cfg[key]) ? addToSet(options.base ? clone(options.base) : {}, cfg[key], options.transform) : fallback; +}; +/** +* Resolve an object-valued configuration option: a prototype-free clone +* of cfg[key] when it is an own, truthy object property, else a fresh +* fallback built by makeFallback (fresh on every parse, so a previous +* parse can never leak state into the next one). +* +* @param cfg the cloned, prototype-free configuration object +* @param key the configuration property to read +* @param makeFallback builds the fallback value when the option is absent +* @returns the resolved object +*/ +const _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback) { + const value = objectHasOwnProperty(cfg, key) ? cfg[key] : void 0; + return value && typeof value === "object" ? clone(value) : makeFallback(); +}; +function createDOMPurify() { + let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal(); + const DOMPurify = (root) => createDOMPurify(root); + DOMPurify.version = "3.4.16"; + DOMPurify.removed = []; + if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) { + DOMPurify.isSupported = false; + return DOMPurify; + } + let document = window.document; + const originalDocument = document; + const currentScript = originalDocument.currentScript; + window.DocumentFragment; + const HTMLTemplateElement = window.HTMLTemplateElement, Node = window.Node, Element = window.Element, NodeFilter = window.NodeFilter; + window.NamedNodeMap === void 0 && (window.NamedNodeMap || window.MozNamedAttrMap); + window.HTMLFormElement; + const DOMParser = window.DOMParser, trustedTypes = window.trustedTypes; + const ElementPrototype = Element.prototype; + const cloneNode = lookupGetter(ElementPrototype, "cloneNode"); + const remove = lookupGetter(ElementPrototype, "remove"); + const removeAttributeNode = lookupGetter(ElementPrototype, "removeAttributeNode"); + const getNextSibling = lookupGetter(ElementPrototype, "nextSibling"); + const getChildNodes = lookupGetter(ElementPrototype, "childNodes"); + const getParentNode = lookupGetter(ElementPrototype, "parentNode"); + const getShadowRoot = lookupGetter(ElementPrototype, "shadowRoot"); + const getAttributes = lookupGetter(ElementPrototype, "attributes"); + const getNodeType = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeType") : null; + const getNodeName = Node && Node.prototype ? lookupGetter(Node.prototype, "nodeName") : null; + const getOwnerDocument = Node && Node.prototype ? lookupGetter(Node.prototype, "ownerDocument") : null; + const _readNodeType = function _readNodeType(node) { + return getNodeType ? getNodeType(node) : node.nodeType; + }; + const _readNodeName = function _readNodeName(node) { + return getNodeName ? getNodeName(node) : node.nodeName; + }; + if (typeof HTMLTemplateElement === "function") { + const template = document.createElement("template"); + if (template.content && template.content.ownerDocument) document = template.content.ownerDocument; + } + let trustedTypesPolicy; + let emptyHTML = ""; + let defaultTrustedTypesPolicy; + let defaultTrustedTypesPolicyResolved = false; + let IN_TRUSTED_TYPES_POLICY = 0; + const _assertNotInTrustedTypesPolicy = function _assertNotInTrustedTypesPolicy() { + if (IN_TRUSTED_TYPES_POLICY > 0) 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."); + }; + const _createTrustedHTML = function _createTrustedHTML(html) { + _assertNotInTrustedTypesPolicy(); + IN_TRUSTED_TYPES_POLICY++; + try { + return trustedTypesPolicy.createHTML(html); + } finally { + IN_TRUSTED_TYPES_POLICY--; + } + }; + const _createTrustedScriptURL = function _createTrustedScriptURL(scriptUrl) { + _assertNotInTrustedTypesPolicy(); + IN_TRUSTED_TYPES_POLICY++; + try { + return trustedTypesPolicy.createScriptURL(scriptUrl); + } finally { + IN_TRUSTED_TYPES_POLICY--; + } + }; + const _getDefaultTrustedTypesPolicy = function _getDefaultTrustedTypesPolicy() { + if (!defaultTrustedTypesPolicyResolved) { + defaultTrustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript); + defaultTrustedTypesPolicyResolved = true; + } + return defaultTrustedTypesPolicy; + }; + const _document = document, implementation = _document.implementation, createNodeIterator = _document.createNodeIterator, createDocumentFragment = _document.createDocumentFragment, getElementsByTagName = _document.getElementsByTagName; + const importNode = originalDocument.importNode; + let hooks = _createHooksMap(); + /** + * Expose whether this browser supports running the full DOMPurify. + */ + DOMPurify.isSupported = typeof entries === "function" && typeof getParentNode === "function" && implementation && implementation.createHTMLDocument !== void 0; + const MUSTACHE_EXPR$1 = MUSTACHE_EXPR, ERB_EXPR$1 = ERB_EXPR, TMPLIT_EXPR$1 = TMPLIT_EXPR, DATA_ATTR$1 = DATA_ATTR, ARIA_ATTR$1 = ARIA_ATTR, IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, ATTR_WHITESPACE$1 = ATTR_WHITESPACE, CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT; + let IS_ALLOWED_URI$1 = IS_ALLOWED_URI; + /** + * We consider the elements and attributes below to be safe. Ideally + * don't add any new ones but feel free to remove unwanted ones. + */ + let ALLOWED_TAGS = null; + const DEFAULT_ALLOWED_TAGS = addToSet({}, [ + ...html$1, + ...svg$1, + ...svgFilters, + ...mathMl$1, + ...text + ]); + let ALLOWED_ATTR = null; + const DEFAULT_ALLOWED_ATTR = addToSet({}, [ + ...html, + ...svg, + ...mathMl, + ...xml + ]); + let CUSTOM_ELEMENT_HANDLING = Object.seal(create(null, { + tagNameCheck: { + writable: true, + configurable: false, + enumerable: true, + value: null + }, + attributeNameCheck: { + writable: true, + configurable: false, + enumerable: true, + value: null + }, + allowCustomizedBuiltInElements: { + writable: true, + configurable: false, + enumerable: true, + value: false + } + })); + let FORBID_TAGS = null; + let FORBID_ATTR = null; + const EXTRA_ELEMENT_HANDLING = Object.seal(create(null, { + tagCheck: { + writable: true, + configurable: false, + enumerable: true, + value: null + }, + attributeCheck: { + writable: true, + configurable: false, + enumerable: true, + value: null + } + })); + let ALLOW_ARIA_ATTR = true; + let ALLOW_DATA_ATTR = true; + let ALLOW_UNKNOWN_PROTOCOLS = false; + let ALLOW_SELF_CLOSE_IN_ATTR = true; + let SAFE_FOR_TEMPLATES = false; + let SAFE_FOR_XML = true; + let WHOLE_DOCUMENT = false; + let SET_CONFIG = false; + let SET_CONFIG_ALLOWED_TAGS = null; + let SET_CONFIG_ALLOWED_ATTR = null; + let FORCE_BODY = false; + let RETURN_DOM = false; + let RETURN_DOM_FRAGMENT = false; + let RETURN_TRUSTED_TYPE = false; + let SANITIZE_DOM = true; + let SANITIZE_NAMED_PROPS = false; + const SANITIZE_NAMED_PROPS_PREFIX = "user-content-"; + let KEEP_CONTENT = true; + let IN_PLACE = false; + let USE_PROFILES = {}; + let FORBID_CONTENTS = null; + const DEFAULT_FORBID_CONTENTS = addToSet({}, [ + "annotation-xml", + "audio", + "colgroup", + "desc", + "foreignobject", + "head", + "iframe", + "math", + "mi", + "mn", + "mo", + "ms", + "mtext", + "noembed", + "noframes", + "noscript", + "plaintext", + "script", + "selectedcontent", + "style", + "svg", + "template", + "thead", + "title", + "video", + "xmp" + ]); + let DATA_URI_TAGS = null; + const DEFAULT_DATA_URI_TAGS = addToSet({}, [ + "audio", + "video", + "img", + "source", + "image", + "track" + ]); + let URI_SAFE_ATTRIBUTES = null; + const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, [ + "alt", + "class", + "for", + "id", + "label", + "name", + "pattern", + "placeholder", + "role", + "summary", + "title", + "value", + "style", + "xmlns" + ]); + const MATHML_NAMESPACE = "http://www.w3.org/1998/Math/MathML"; + const SVG_NAMESPACE = "http://www.w3.org/2000/svg"; + const HTML_NAMESPACE = "http://www.w3.org/1999/xhtml"; + let NAMESPACE = HTML_NAMESPACE; + let IS_EMPTY_INPUT = false; + let ALLOWED_NAMESPACES = null; + const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [ + MATHML_NAMESPACE, + SVG_NAMESPACE, + HTML_NAMESPACE + ], stringToString); + const DEFAULT_MATHML_TEXT_INTEGRATION_POINTS = freeze([ + "mi", + "mo", + "mn", + "ms", + "mtext" + ]); + let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS); + const DEFAULT_HTML_INTEGRATION_POINTS = freeze(["annotation-xml"]); + let HTML_INTEGRATION_POINTS = addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS); + const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, [ + "title", + "style", + "font", + "a", + "script" + ]); + let PARSER_MEDIA_TYPE = null; + const SUPPORTED_PARSER_MEDIA_TYPES = ["application/xhtml+xml", "text/html"]; + const DEFAULT_PARSER_MEDIA_TYPE = "text/html"; + let transformCaseFunc = null; + let CONFIG = null; + const formElement = document.createElement("form"); + const isRegexOrFunction = function isRegexOrFunction(testValue) { + return testValue instanceof RegExp || testValue instanceof Function; + }; + /** + * _parseConfig + * + * @param cfg optional config literal + */ + const _parseConfig = function _parseConfig() { + let cfg = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; + if (CONFIG && CONFIG === cfg) return; + if (!cfg || typeof cfg !== "object") cfg = {}; + cfg = clone(cfg); + PARSER_MEDIA_TYPE = SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE; + transformCaseFunc = PARSER_MEDIA_TYPE === "application/xhtml+xml" ? stringToString : stringToLowerCase; + ALLOWED_TAGS = _resolveSetOption(cfg, "ALLOWED_TAGS", DEFAULT_ALLOWED_TAGS, { transform: transformCaseFunc }); + ALLOWED_ATTR = _resolveSetOption(cfg, "ALLOWED_ATTR", DEFAULT_ALLOWED_ATTR, { transform: transformCaseFunc }); + ALLOWED_NAMESPACES = _resolveSetOption(cfg, "ALLOWED_NAMESPACES", DEFAULT_ALLOWED_NAMESPACES, { transform: stringToString }); + URI_SAFE_ATTRIBUTES = _resolveSetOption(cfg, "ADD_URI_SAFE_ATTR", DEFAULT_URI_SAFE_ATTRIBUTES, { + transform: transformCaseFunc, + base: DEFAULT_URI_SAFE_ATTRIBUTES + }); + DATA_URI_TAGS = _resolveSetOption(cfg, "ADD_DATA_URI_TAGS", DEFAULT_DATA_URI_TAGS, { + transform: transformCaseFunc, + base: DEFAULT_DATA_URI_TAGS + }); + FORBID_CONTENTS = _resolveSetOption(cfg, "FORBID_CONTENTS", DEFAULT_FORBID_CONTENTS, { transform: transformCaseFunc }); + FORBID_TAGS = _resolveSetOption(cfg, "FORBID_TAGS", clone({}), { transform: transformCaseFunc }); + FORBID_ATTR = _resolveSetOption(cfg, "FORBID_ATTR", clone({}), { transform: transformCaseFunc }); + USE_PROFILES = objectHasOwnProperty(cfg, "USE_PROFILES") ? cfg.USE_PROFILES && typeof cfg.USE_PROFILES === "object" ? clone(cfg.USE_PROFILES) : cfg.USE_PROFILES : false; + ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; + ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; + ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; + ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; + SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; + SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; + WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; + RETURN_DOM = cfg.RETURN_DOM || false; + RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; + RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; + FORCE_BODY = cfg.FORCE_BODY || false; + SANITIZE_DOM = cfg.SANITIZE_DOM !== false; + SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; + KEEP_CONTENT = cfg.KEEP_CONTENT !== false; + IN_PLACE = cfg.IN_PLACE || false; + IS_ALLOWED_URI$1 = isRegex(cfg.ALLOWED_URI_REGEXP) ? cfg.ALLOWED_URI_REGEXP : IS_ALLOWED_URI; + NAMESPACE = typeof cfg.NAMESPACE === "string" ? cfg.NAMESPACE : HTML_NAMESPACE; + MATHML_TEXT_INTEGRATION_POINTS = _resolveObjectOption(cfg, "MATHML_TEXT_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_MATHML_TEXT_INTEGRATION_POINTS)); + HTML_INTEGRATION_POINTS = _resolveObjectOption(cfg, "HTML_INTEGRATION_POINTS", () => addToSet({}, DEFAULT_HTML_INTEGRATION_POINTS)); + const customElementHandling = _resolveObjectOption(cfg, "CUSTOM_ELEMENT_HANDLING", () => create(null)); + CUSTOM_ELEMENT_HANDLING = create(null); + if (objectHasOwnProperty(customElementHandling, "tagNameCheck") && isRegexOrFunction(customElementHandling.tagNameCheck)) CUSTOM_ELEMENT_HANDLING.tagNameCheck = customElementHandling.tagNameCheck; + if (objectHasOwnProperty(customElementHandling, "attributeNameCheck") && isRegexOrFunction(customElementHandling.attributeNameCheck)) CUSTOM_ELEMENT_HANDLING.attributeNameCheck = customElementHandling.attributeNameCheck; + if (objectHasOwnProperty(customElementHandling, "allowCustomizedBuiltInElements") && typeof customElementHandling.allowCustomizedBuiltInElements === "boolean") CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = customElementHandling.allowCustomizedBuiltInElements; + seal(CUSTOM_ELEMENT_HANDLING); + if (SAFE_FOR_TEMPLATES) ALLOW_DATA_ATTR = false; + if (RETURN_DOM_FRAGMENT) RETURN_DOM = true; + if (USE_PROFILES) { + ALLOWED_TAGS = addToSet({}, text); + ALLOWED_ATTR = create(null); + if (USE_PROFILES.html === true) { + addToSet(ALLOWED_TAGS, html$1); + addToSet(ALLOWED_ATTR, html); + } + if (USE_PROFILES.svg === true) { + addToSet(ALLOWED_TAGS, svg$1); + addToSet(ALLOWED_ATTR, svg); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.svgFilters === true) { + addToSet(ALLOWED_TAGS, svgFilters); + addToSet(ALLOWED_ATTR, svg); + addToSet(ALLOWED_ATTR, xml); + } + if (USE_PROFILES.mathMl === true) { + addToSet(ALLOWED_TAGS, mathMl$1); + addToSet(ALLOWED_ATTR, mathMl); + addToSet(ALLOWED_ATTR, xml); + } + } + EXTRA_ELEMENT_HANDLING.tagCheck = null; + EXTRA_ELEMENT_HANDLING.attributeCheck = null; + if (objectHasOwnProperty(cfg, "ADD_TAGS")) { + if (typeof cfg.ADD_TAGS === "function") EXTRA_ELEMENT_HANDLING.tagCheck = cfg.ADD_TAGS; + else if (arrayIsArray(cfg.ADD_TAGS)) { + if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) ALLOWED_TAGS = clone(ALLOWED_TAGS); + addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); + } + } + if (objectHasOwnProperty(cfg, "ADD_ATTR")) { + if (typeof cfg.ADD_ATTR === "function") EXTRA_ELEMENT_HANDLING.attributeCheck = cfg.ADD_ATTR; + else if (arrayIsArray(cfg.ADD_ATTR)) { + if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) ALLOWED_ATTR = clone(ALLOWED_ATTR); + addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); + } + } + if (objectHasOwnProperty(cfg, "ADD_FORBID_CONTENTS") && arrayIsArray(cfg.ADD_FORBID_CONTENTS)) { + if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) FORBID_CONTENTS = clone(FORBID_CONTENTS); + addToSet(FORBID_CONTENTS, cfg.ADD_FORBID_CONTENTS, transformCaseFunc); + } + if (KEEP_CONTENT) ALLOWED_TAGS["#text"] = true; + if (WHOLE_DOCUMENT) addToSet(ALLOWED_TAGS, [ + "html", + "head", + "body" + ]); + if (ALLOWED_TAGS.table) { + addToSet(ALLOWED_TAGS, ["tbody"]); + delete FORBID_TAGS.tbody; + } + if (cfg.TRUSTED_TYPES_POLICY) { + if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== "function") throw typeErrorCreate("TRUSTED_TYPES_POLICY configuration option must provide a \"createHTML\" hook."); + if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== "function") throw typeErrorCreate("TRUSTED_TYPES_POLICY configuration option must provide a \"createScriptURL\" hook."); + const previousTrustedTypesPolicy = trustedTypesPolicy; + trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY; + try { + emptyHTML = _createTrustedHTML(""); + } catch (error) { + trustedTypesPolicy = previousTrustedTypesPolicy; + throw error; + } + } else if (cfg.TRUSTED_TYPES_POLICY === null) { + trustedTypesPolicy = void 0; + emptyHTML = ""; + } else { + if (trustedTypesPolicy === void 0) trustedTypesPolicy = _getDefaultTrustedTypesPolicy(); + if (trustedTypesPolicy && typeof emptyHTML === "string") emptyHTML = _createTrustedHTML(""); + } + if (freeze) freeze(cfg); + CONFIG = cfg; + }; + const ALL_SVG_TAGS = addToSet({}, [ + ...svg$1, + ...svgFilters, + ...svgDisallowed + ]); + const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]); + /** + * Namespace rules for an element in the SVG namespace. + * + * @param tagName the element's lowercase tag name + * @param parent the (possibly simulated) parent node + * @param parentTagName the parent's lowercase tag name + * @returns true if a spec-compliant parser could produce this element + */ + const _checkSvgNamespace = function _checkSvgNamespace(tagName, parent, parentTagName) { + if (parent.namespaceURI === HTML_NAMESPACE) return tagName === "svg"; + if (parent.namespaceURI === MATHML_NAMESPACE) return tagName === "svg" && (parentTagName === "annotation-xml" || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); + return Boolean(ALL_SVG_TAGS[tagName]); + }; + /** + * Namespace rules for an element in the MathML namespace. + * + * @param tagName the element's lowercase tag name + * @param parent the (possibly simulated) parent node + * @param parentTagName the parent's lowercase tag name + * @returns true if a spec-compliant parser could produce this element + */ + const _checkMathMlNamespace = function _checkMathMlNamespace(tagName, parent, parentTagName) { + if (parent.namespaceURI === HTML_NAMESPACE) return tagName === "math"; + if (parent.namespaceURI === SVG_NAMESPACE) return tagName === "math" && HTML_INTEGRATION_POINTS[parentTagName]; + return Boolean(ALL_MATHML_TAGS[tagName]); + }; + /** + * Namespace rules for an element in the HTML namespace. + * + * @param tagName the element's lowercase tag name + * @param parent the (possibly simulated) parent node + * @param parentTagName the parent's lowercase tag name + * @returns true if a spec-compliant parser could produce this element + */ + const _checkHtmlNamespace = function _checkHtmlNamespace(tagName, parent, parentTagName) { + if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) return false; + if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) return false; + return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); + }; + /** + * @param element a DOM element whose namespace is being checked + * @returns Return false if the element has a + * namespace that a spec-compliant parser would never + * return. Return true otherwise. + */ + const _checkValidNamespace = function _checkValidNamespace(element) { + let parent = getParentNode(element); + if (!parent || !parent.tagName) parent = { + namespaceURI: NAMESPACE, + tagName: "template" + }; + const tagName = stringToLowerCase(element.tagName); + const parentTagName = stringToLowerCase(parent.tagName); + if (!ALLOWED_NAMESPACES[element.namespaceURI]) return false; + if (element.namespaceURI === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName); + if (element.namespaceURI === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName); + if (element.namespaceURI === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName); + if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) return true; + return false; + }; + /** + * _forceRemove + * + * @param node a DOM node + */ + const _forceRemove = function _forceRemove(node) { + arrayPush(DOMPurify.removed, { element: node }); + try { + getParentNode(node).removeChild(node); + } catch (_) { + remove(node); + if (!getParentNode(node)) throw typeErrorCreate("a node selected for removal could not be detached from its tree and cannot be safely returned; refusing to sanitize in place"); + } + }; + /** + * _stripAttributeNode + * + * Remove a single Attr node case/namespace-exactly on an attribute-teardown + * path. Name-based removeAttribute() ASCII-lowercases its lookup key for an + * HTML element in an HTML document and so silently misses a case-preserved + * handler (e.g. `ONERROR` off an XML/XHTML import) - the same defect + * _removeAttribute() was fixed for, which a name-based call would reintroduce + * on these IN_PLACE teardown paths. Unlike _removeAttribute this does not + * record into DOMPurify.removed: the neutralize passes intentionally do not + * book-keep. A clobbered/detached node falls back to best-effort name-based + * removal. + * + * @param element the element to strip the attribute from + * @param attribute the Attr node to remove + * @param name the attribute's name, for the fallback path + */ + const _stripAttributeNode = function _stripAttributeNode(element, attribute, name) { + try { + removeAttributeNode(element, attribute); + } catch (_) { + try { + element.removeAttribute(name); + } catch (_) {} + } + }; + /** + * _neutralizeRoot + * + * Fail-closed teardown of an in-place root after the sanitize walk aborts + * (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered + * custom element's reaction detaches a node so `_forceRemove`'s deliberate + * parentless guard throws, or any other re-entrant engine mutation — would + * otherwise leave the caller's *live* tree half-sanitized, with everything + * after the abort point still carrying its handlers. There is no safe way + * to resume the walk (the tree mutated under us), so we strip the root bare: + * remove every child and every attribute, then let the caller's catch see + * the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes` + * getters; the root was already clobber-pre-flighted at the IN_PLACE entry). + * + * @param root the in-place root to empty + */ + const _neutralizeRoot = function _neutralizeRoot(root) { + _neutralizeSubtree(root); + const childNodes = getChildNodes(root); + if (childNodes) { + const snapshot = []; + arrayForEach(childNodes, (child) => { + arrayPush(snapshot, child); + }); + arrayForEach(snapshot, (child) => { + try { + remove(child); + } catch (_) {} + }); + } + const attributes = getAttributes(root); + if (attributes) for (let i = attributes.length - 1; i >= 0; --i) { + const attribute = attributes[i]; + const name = attribute && attribute.name; + if (typeof name === "string") _stripAttributeNode(root, attribute, name); + } + }; + /** + * _removeAttribute + * + * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their + * lookup key for HTML elements in an HTML document, so they silently miss an + * attribute whose stored qualified name still contains uppercase ASCII + * letters. That happens when the node came from a case-preserving source + * (an XML/XHTML document imported via importNode(), or createAttributeNS()), + * where e.g. `ONERROR` survives the walk: the policy check lowercases to + * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up + * `onerror` and finds nothing. Remove the exact Attr node instead, which is + * case- and namespace-exact, and fall back to name-based removal only when + * the caller could not supply the node. + * + * @param name an Attribute name + * @param element a DOM node + * @param attr the exact Attr node to remove, when the caller has it + */ + const _removeAttribute = function _removeAttribute(name, element, attr) { + if (!attr) try { + attr = element.getAttributeNode(name); + } catch (_) { + attr = null; + } + arrayPush(DOMPurify.removed, { + attribute: attr || null, + from: element + }); + try { + if (attr) removeAttributeNode(element, attr); + else element.removeAttribute(name); + } catch (_) { + try { + element.removeAttribute(name); + } catch (_) {} + } + if (name === "is") { + if (RETURN_DOM || RETURN_DOM_FRAGMENT) try { + _forceRemove(element); + } catch (_) {} + else try { + element.setAttribute(name, ""); + } catch (_) {} + } + }; + /** + * _stripDisallowedAttributes + * + * Removes every attribute the active configuration does not allow from a + * single element, using the same allowlist as the main attribute pass (so + * `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to + * neutralise nodes that are being discarded from an in-place tree. + * + * @param element the element to strip + */ + const _stripDisallowedAttributes = function _stripDisallowedAttributes(element) { + const attributes = getAttributes(element); + if (!attributes) return; + for (let i = attributes.length - 1; i >= 0; --i) { + const attribute = attributes[i]; + const name = attribute && attribute.name; + if (typeof name !== "string" || ALLOWED_ATTR[transformCaseFunc(name)]) continue; + _stripAttributeNode(element, attribute, name); + } + }; + /** + * _neutralizeSubtree + * + * Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT + * move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary, + * namespace, comment, processing-instruction and KEEP_CONTENT:false removals + * all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path + * those dropped nodes are detached from the caller's LIVE tree but a + * handler-bearing original among them (an ``/`