chore: refactoring run pt4, demo touchup, docs, comments, small things (#1476)

This commit is contained in:
Cure53
2026-06-12 12:41:53 +02:00
committed by GitHub
parent 9ef570a0d4
commit adecaa0e37
26 changed files with 296 additions and 241 deletions
+21 -9
View File
@@ -70,7 +70,10 @@ concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
# Fast feedback: lint, jsdom, chromium-only browser test across Node versions.
# Fast feedback: lint, build, dist-sync and jsdom across all supported
# Node versions. The full three-engine browser suite runs once, on the
# primary Node version only - browser behaviour is Node-independent, so
# repeating it per Node entry added cost without coverage.
# Runs on every push and every PR.
install:
runs-on: ubuntu-latest
@@ -99,11 +102,15 @@ jobs:
- name: Install Dependencies
run: npm ci --ignore-scripts
# All three engines - test:ci runs the full browser matrix (no
# --project flag, unlike local `npm test`). DOMPurify's security
# posture depends on per-engine HTML parser behaviour being exercised,
# so this coverage is kept on every PR despite the download cost.
# All three engines, but only on the primary Node version (the same
# one the browser-matrix job uses). DOMPurify's security posture
# depends on per-engine HTML parser behaviour being exercised on
# every PR - but the browsers don't care which Node drives them, so
# installing and running 3 engines x 5 Node versions repeated the
# same suite 15 times. This gate keeps the full per-engine coverage
# once per PR and drops the 12 redundant runs.
- name: Install Playwright Browsers
if: matrix.node-version == '25.x'
run: ./node_modules/.bin/playwright install --with-deps chromium firefox webkit
- name: Build
@@ -123,11 +130,16 @@ jobs:
- name: Lint
run: npm run lint
- name: Test
- name: Test (jsdom + full browser matrix)
if: matrix.node-version == '25.x'
run: npm run test:ci
- name: Test (jsdom)
if: matrix.node-version != '25.x'
run: npm run test:jsdom
- name: Upload Playwright report
if: failure()
if: failure() && matrix.node-version == '25.x'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-report-node-${{ matrix.node-version }}
@@ -142,8 +154,8 @@ jobs:
# Safari engine, Windows font shaping, Linux-specific parser paths).
#
# Only runs on release branches (main, 2.x, 3.x) to conserve runner minutes.
# PRs get chromium-only coverage from the "install" job above, which is
# sufficient for catching regressions.
# PRs get full three-engine coverage on a single Node version from the
# "install" job above, which is sufficient for catching regressions.
browser-matrix:
if: github.event_name == 'push'
runs-on: ${{ matrix.os }}
+2
View File
@@ -505,10 +505,12 @@ These are our npm scripts:
- `npm run dev` to build the unminified UMD bundle while watching sources for changes
- `npm run test` to lint the sources, run tests through jsdom, and run browser tests in Chromium via Playwright
- `npm run test:jsdom` to only run tests through jsdom
- `npm run test:happydom` to run the suite through happy-dom (an unsupported environment; kept as a robustness check, not a compatibility promise)
- `npm run test:browser` to only run tests through Playwright
- `npm run test:browser:legacy` to run the suite on older browser engines (point `PW_MODULE` at a pinned old Playwright install; see `.github/workflows/legacy-browsers.yml`)
- `npm run test:ci` to run the CI test flow for jsdom and Playwright
- `npm run test:fuzz` to run a small fuzzer covering `sanitize()` and CONFIG
- `npm run bench` to run the jsdom micro-benchmark over the built `dist/purify.cjs` (build first; `--json` and `--compare a.json b.json` support A/B runs across branches - results are directional, confirm user-facing claims in real browsers)
- `npm run coverage` to build an instrumented bundle, run the jsdom suite, and write a local HTML line/branch coverage report to `coverage/index.html` (jsdom scope only, not run in CI)
- `npm run build:cov` to only build the instrumented coverage bundle
- `npm run lint` to lint the sources using ESLint via xo
+51 -14
View File
@@ -2,11 +2,11 @@
This is a collection of demos of how to use DOMPurify. When run without any additional configuration parameters, DOMPurify will reliably protect against XSS and DOM Clobbering attacks. But there is a lot of more things this library can do. For example proxy all HTTP resources, enhance accessibility, prevent links to open in the same window, allow and sand-box JavaScript and more.
This collection of demos shows to same code for several different ways you can use DOMPurify. Please feel free to suggest additional demos if needed. All demos we have collected so far will be shown and explained below.
This collection of demos shows sample code for several different ways you can use DOMPurify. Please feel free to suggest additional demos if needed. All demos we have collected so far will be shown and explained below.
### Basic Demo [Link](basic-demo.html)
This is the most basic of all demos. It shows how you user DOMPurify and that's it. No configuration, no hooks, no extras. Just DOMPurify running with default settings.
This is the most basic of all demos. It shows how you use DOMPurify and that's it. No configuration, no hooks, no extras. Just DOMPurify running with default settings.
This is the relevant code:
@@ -94,6 +94,44 @@ console.log(DOMPurify.removeHook('beforeSanitizeAttributes'));
let clean = DOMPurify.sanitize(dirty);
```
### Hook to remove empty elements [Link](hooks-node-removal-demo.html)
This demo shows how to use the `uponSanitizeElement` hook to remove elements that have no child nodes and no text content, cleaning empty leftovers out of the sanitized markup.
This is the relevant code:
```javascript
DOMPurify.addHook('uponSanitizeElement', (node) => {
if (!node.hasChildNodes() && !node.textContent) {
node.remove();
}
});
```
### Hook to remove an element and its content [Link](hooks-node-removal2-demo.html)
`ALLOWED_TAGS` alone strips a tag but keeps its content, and `FORBID_TAGS` cannot be combined with `ALLOWED_TAGS`. To remove an element _including_ everything inside it, allow the tag and remove the node in a hook. Note that the node must be on the allow-list for the hook to see it.
This is the relevant code:
```javascript
DOMPurify.setConfig({ ALLOWED_TAGS: ['div', '#text', 'footer'] });
DOMPurify.addHook('uponSanitizeElement', (node) => {
if (node.tagName === 'FOOTER') {
node.remove();
}
});
```
### Hook to sanitize CSS [Link](hooks-sanitize-css-demo.html)
This demo walks the CSS rules of permitted `<style>` elements inside a hook and removes declarations that are not on a small property allow-list, so even allow-listed style content cannot smuggle in unwanted CSS.
### Trusted Types demo [Link](trusted-types-demo.html)
This demo shows DOMPurify under a `trusted-types` Content Security Policy: the `data-tt-policy-suffix` attribute, `RETURN_TRUSTED_TYPE: true`, and wrapping DOMPurify in a policy of your own. It needs a browser with Trusted Types support to do anything.
### Hook to open all links in a new window [Link](hooks-target-blank-demo.html)
This hook is an important one and used quite commonly. It is made to assure that all elements that can function as a link will open the linked page in a new tab or window. This is often of great importance in web mailers and other tools, where a click on a link is not supposed to navigate the original page but rather open another window or tab.
@@ -415,22 +453,21 @@ DOMPurify can be used to sanitize SVGs, but there can be some issues with some o
Here is an example which works well for content generated by Illustrator:
```javascript
// Add a hook to post-process a sanitized SVG
// Add a hook to post-process the sanitized SVG root: re-add the
// namespaces (added by tools like Illustrator, stripped during
// sanitization) that an <img>-embedded SVG needs to render
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
// Fix namespaces added by Adobe Illustrator
node.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
node.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
if (node.tagName && node.tagName.toLowerCase() === 'svg') {
node.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
node.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
}
});
// Clean SVG string and allow the "filter" tag
// Clean the SVG string and allow the "filter" tag
const clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['filter'] });
// Remove partial XML comment left in the HTML
let badTag = clean.indexOf(']&gt;');
let pureSvg = clean.substring(badTag < 0 ? 0 : 5, clean.length);
// Show sanitized content in <img> element
let img = new Image();
img.src = 'data:image/svg+xml;base64,' + window.btoa(pureSvg);
// Show the sanitized content in an <img> element
const img = new Image();
img.src = 'data:image/svg+xml;base64,' + window.btoa(clean);
document.getElementById('sanitized').appendChild(img);
```
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Advanced config</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Basic usage</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Config usage</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hooks</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to proxy all links</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+9 -4
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to sandbox JavaScript via MentalJS</title>
<script src="../dist/purify.js"></script>
<!-- Grab the latest version of MentalJS -->
<script src="./lib/Mental.js"></script>
@@ -34,10 +36,14 @@
DOMPurify.addHook('uponSanitizeElement', (node, data) => {
if (data.tagName === 'script') {
const script = node.textContent;
// Note: we never remove the <script> node ourselves -
// it is not an allowed tag, so DOMPurify removes it
// after this hook returns. Pre-detaching it here would
// trip DOMPurify's fail-closed removal guard.
if (!script || 'src' in node.attributes
|| 'href' in node.attributes
|| 'xlink:href' in node.attributes) {
return node.parentNode.removeChild(node);
return;
}
try {
const mental = MentalJS().parse({
@@ -47,9 +53,8 @@
const scriptNode = document.createElement('script');
scriptNode.appendChild(document.createTextNode(mental));
document.head.appendChild(scriptNode);
return node.parentNode.removeChild(node);
} catch (e) {
return node.parentNode.removeChild(node);
// parse failed - DOMPurify removes the node anyway
}
}
});
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to remove empty elements</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+4 -2
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to remove an element and its content</title>
<script src="../dist/purify.js"></script>
</head>
<body>
@@ -52,7 +54,7 @@
// Clean HTML string and write into our DIV
const clean = DOMPurify.sanitize(dirty);
document.getElementById('sanitized').innerText = clean;
document.getElementById('sanitized').textContent = clean;
</script>
</body>
</html>
+8 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to proxy all HTTP leaks including CSS</title>
<script src="../dist/purify.js"></script>
</head>
<body>
@@ -71,6 +73,11 @@
DOMPurify.addHook('uponSanitizeElement', (node, data) => {
if (data.tagName === 'style') {
const output = [];
// Some DOM implementations (e.g. jsdom) do not
// populate .sheet for styles in the inert document
if (!node.sheet) {
return;
}
addCSSRules(output, node.sheet.cssRules);
node.textContent = output.join("\n");
}
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Adding and removing hooks</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+8 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to sanitize CSS</title>
<script src="../dist/purify.js"></script>
</head>
<body>
@@ -75,6 +77,11 @@
DOMPurify.addHook('uponSanitizeElement', function(node, data) {
if (data.tagName === 'style') {
let output = [];
// Some DOM implementations (e.g. jsdom) do not
// populate .sheet for styles in the inert document
if (!node.sheet) {
return;
}
addCSSRules(output, node.sheet.cssRules);
node.textContent = output.join("\n");
}
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to allowlist safe URI schemes</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+47
View File
@@ -0,0 +1,47 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Sanitize SVGs shown via an img tag</title>
<script src="../dist/purify.js"></script>
</head>
<body>
<!-- Our DIV to receive content -->
<div id="sanitized"></div>
<!-- Now let's sanitize an SVG and show it via an <img> -->
<script>
'use strict';
// An SVG as exported by Adobe Illustrator, including the
// namespace declarations DOMPurify strips by default and a
// <filter> element, which is not allowed by default.
const dirty = `<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 100 100">
<filter id="blur"><feGaussianBlur stdDeviation="2"/></filter>
<circle cx="50" cy="50" r="40" fill="teal" filter="url(#blur)"
onclick="alert(document.domain)"/>
</svg>`;
// Add a hook to post-process the sanitized SVG root: re-add the
// namespaces (added by tools like Illustrator, stripped during
// sanitization) that an <img>-embedded SVG needs to render.
DOMPurify.addHook('afterSanitizeAttributes', function (node) {
if (node.tagName && node.tagName.toLowerCase() === 'svg') {
node.setAttribute('xmlns', 'http://www.w3.org/2000/svg');
node.setAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
}
});
// Clean the SVG string and allow the "filter" tag
const clean = DOMPurify.sanitize(dirty, { ADD_TAGS: ['filter'] });
// Show the sanitized content in an <img> element
const img = new Image();
img.src = 'data:image/svg+xml;base64,' + window.btoa(clean);
document.getElementById('sanitized').appendChild(img);
</script>
</body>
</html>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Hook to open all links in a new window</title>
<script src="../dist/purify.js"></script>
</head>
<body>
+3 -1
View File
@@ -1,6 +1,8 @@
<!doctype html>
<html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>DOMPurify demo: Trusted Types</title>
<meta http-equiv="Content-Security-Policy" content="trusted-types dompurify#demo sanitize-using-dompurify my-policy">
<script src="../dist/purify.js" data-tt-policy-suffix="demo"></script>
<style>
+28 -49
View File
@@ -410,20 +410,15 @@ const _createHooksMap = function _createHooksMap() {
};
};
/**
* Resolve a set-valued configuration option.
*
* When `cfg` carries an own array property under `key`, build a fresh set
* from it - seeded with a clone of `base` when given, an empty set
* otherwise - using `transform` for case normalization. Otherwise return
* `fallback` as-is. Mirrors the long-standing inline pattern exactly: a
* fresh set object per parse on the array path, the (possibly shared)
* default on the fallback path.
* 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: case-transform function handed to addToSet;
* base: optional default set to merge the supplied values into
* @param options transform and optional base set to merge into
* @returns the resolved set
*/
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
@@ -972,9 +967,9 @@ function createDOMPurify() {
if (parent.namespaceURI === HTML_NAMESPACE) {
return tagName === 'svg';
}
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
// The only way to switch from MathML to SVG is via <svg>
// if the parent is either <annotation-xml> or a MathML
// text integration point.
if (parent.namespaceURI === MATHML_NAMESPACE) {
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
}
@@ -1311,10 +1306,9 @@ function createDOMPurify() {
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
};
/**
* Replace template expression syntax (mustache, ERB, template literal)
* with a space, in the same order the previous inline arrayForEach
* applied. One shared helper avoids re-allocating the expression array
* and iteration closure at every scrub site.
* Replace template expression syntax (mustache, ERB, template
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
* sites. Order matters: mustache, then ERB, then template literal.
*
* @param value the string to scrub
* @returns the scrubbed string
@@ -1469,14 +1463,10 @@ function createDOMPurify() {
});
}
/**
* _isUnsafeNode
*
* Structural-threat checks that condemn a node regardless of the
* allowlists: mXSS via namespace confusion, risky CSS construction,
* processing instructions, and comments with markup-significant
* content. Pure predicate - reads only; the caller performs the
* removal. Check order is load-bearing and mirrors the original
* inline sequence.
* processing instructions, markup-bearing comments. Pure predicate;
* the caller removes. Check order is load-bearing.
*
* @param currentNode the node to inspect
* @param tagName the node's transformCaseFunc'd tag name
@@ -1502,15 +1492,11 @@ function createDOMPurify() {
return false;
};
/**
* _sanitizeDisallowedNode
*
* Handles a node whose tag is not on the active allowlist (or is
* explicitly forbidden). Keeps allowed custom elements (returning
* false exits _sanitizeElements early, exactly as the previous
* inline code did - the namespace and fallback-tag checks and the
* afterSanitizeElements hook are intentionally skipped for kept
* custom elements). Otherwise hoists content per KEEP_CONTENT and
* removes the node.
* Handle a node whose tag is forbidden or not allowlisted: keep
* allowed custom elements (false return exits _sanitizeElements
* early - namespace/fallback checks and the afterSanitizeElements
* hook are intentionally skipped for kept custom elements), else
* hoist content per KEEP_CONTENT and remove.
*
* @param currentNode the disallowed node
* @param tagName the node's transformCaseFunc'd tag name
@@ -1687,12 +1673,10 @@ function createDOMPurify() {
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
};
/**
* _applyTrustedTypesToAttribute
*
* Wrap an attribute value in the matching Trusted Types object when
* the active policy and browser support require it. Namespaced
* attributes are returned unchanged (Trusted Types does not support
* them yet, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
* the active policy requires it. Namespaced attributes pass through
* unchanged (no TT support yet, see
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
*
* @param lcTag lowercase tag name of the containing element
* @param lcName lowercase attribute name
@@ -1716,17 +1700,12 @@ function createDOMPurify() {
return value;
};
/**
* _setAttributeValue
*
* Write a (hook- or named-props-)modified attribute value back onto
* the element. On success, re-probe for clobbering introduced by the
* new value and remove the element when found; otherwise pop the
* removal entry recorded by the earlier _removeAttribute. On failure
* (e.g. an invalid data-* name for setAttributeNS), remove the
* attribute instead. Moved verbatim from the _sanitizeAttributes
* loop body - including the arrayPop bookkeeping, whose pairing with
* the SANITIZE_NAMED_PROPS _removeAttribute call above it is
* long-standing behavior.
* Write a modified attribute value back onto the element. On
* success, re-probe for clobbering introduced by the new value and
* remove the element when found; otherwise pop the removal entry
* recorded by the earlier _removeAttribute (long-standing pairing
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
* failure, remove the attribute instead.
*
* @param currentNode the element carrying the attribute
* @param name the attribute name as present on the element
@@ -1814,7 +1793,7 @@ function createDOMPurify() {
_removeAttribute(name, currentNode);
continue;
}
/* Did the hooks approve of the attribute? */
/* Did the hooks force-keep the attribute? */
if (hookEvent.forceKeepAttr) {
continue;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+28 -49
View File
@@ -408,20 +408,15 @@ const _createHooksMap = function _createHooksMap() {
};
};
/**
* Resolve a set-valued configuration option.
*
* When `cfg` carries an own array property under `key`, build a fresh set
* from it - seeded with a clone of `base` when given, an empty set
* otherwise - using `transform` for case normalization. Otherwise return
* `fallback` as-is. Mirrors the long-standing inline pattern exactly: a
* fresh set object per parse on the array path, the (possibly shared)
* default on the fallback path.
* 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: case-transform function handed to addToSet;
* base: optional default set to merge the supplied values into
* @param options transform and optional base set to merge into
* @returns the resolved set
*/
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
@@ -970,9 +965,9 @@ function createDOMPurify() {
if (parent.namespaceURI === HTML_NAMESPACE) {
return tagName === 'svg';
}
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
// The only way to switch from MathML to SVG is via <svg>
// if the parent is either <annotation-xml> or a MathML
// text integration point.
if (parent.namespaceURI === MATHML_NAMESPACE) {
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
}
@@ -1309,10 +1304,9 @@ function createDOMPurify() {
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
};
/**
* Replace template expression syntax (mustache, ERB, template literal)
* with a space, in the same order the previous inline arrayForEach
* applied. One shared helper avoids re-allocating the expression array
* and iteration closure at every scrub site.
* Replace template expression syntax (mustache, ERB, template
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
* sites. Order matters: mustache, then ERB, then template literal.
*
* @param value the string to scrub
* @returns the scrubbed string
@@ -1467,14 +1461,10 @@ function createDOMPurify() {
});
}
/**
* _isUnsafeNode
*
* Structural-threat checks that condemn a node regardless of the
* allowlists: mXSS via namespace confusion, risky CSS construction,
* processing instructions, and comments with markup-significant
* content. Pure predicate - reads only; the caller performs the
* removal. Check order is load-bearing and mirrors the original
* inline sequence.
* processing instructions, markup-bearing comments. Pure predicate;
* the caller removes. Check order is load-bearing.
*
* @param currentNode the node to inspect
* @param tagName the node's transformCaseFunc'd tag name
@@ -1500,15 +1490,11 @@ function createDOMPurify() {
return false;
};
/**
* _sanitizeDisallowedNode
*
* Handles a node whose tag is not on the active allowlist (or is
* explicitly forbidden). Keeps allowed custom elements (returning
* false exits _sanitizeElements early, exactly as the previous
* inline code did - the namespace and fallback-tag checks and the
* afterSanitizeElements hook are intentionally skipped for kept
* custom elements). Otherwise hoists content per KEEP_CONTENT and
* removes the node.
* Handle a node whose tag is forbidden or not allowlisted: keep
* allowed custom elements (false return exits _sanitizeElements
* early - namespace/fallback checks and the afterSanitizeElements
* hook are intentionally skipped for kept custom elements), else
* hoist content per KEEP_CONTENT and remove.
*
* @param currentNode the disallowed node
* @param tagName the node's transformCaseFunc'd tag name
@@ -1685,12 +1671,10 @@ function createDOMPurify() {
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
};
/**
* _applyTrustedTypesToAttribute
*
* Wrap an attribute value in the matching Trusted Types object when
* the active policy and browser support require it. Namespaced
* attributes are returned unchanged (Trusted Types does not support
* them yet, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
* the active policy requires it. Namespaced attributes pass through
* unchanged (no TT support yet, see
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
*
* @param lcTag lowercase tag name of the containing element
* @param lcName lowercase attribute name
@@ -1714,17 +1698,12 @@ function createDOMPurify() {
return value;
};
/**
* _setAttributeValue
*
* Write a (hook- or named-props-)modified attribute value back onto
* the element. On success, re-probe for clobbering introduced by the
* new value and remove the element when found; otherwise pop the
* removal entry recorded by the earlier _removeAttribute. On failure
* (e.g. an invalid data-* name for setAttributeNS), remove the
* attribute instead. Moved verbatim from the _sanitizeAttributes
* loop body - including the arrayPop bookkeeping, whose pairing with
* the SANITIZE_NAMED_PROPS _removeAttribute call above it is
* long-standing behavior.
* Write a modified attribute value back onto the element. On
* success, re-probe for clobbering introduced by the new value and
* remove the element when found; otherwise pop the removal entry
* recorded by the earlier _removeAttribute (long-standing pairing
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
* failure, remove the attribute instead.
*
* @param currentNode the element carrying the attribute
* @param name the attribute name as present on the element
@@ -1812,7 +1791,7 @@ function createDOMPurify() {
_removeAttribute(name, currentNode);
continue;
}
/* Did the hooks approve of the attribute? */
/* Did the hooks force-keep the attribute? */
if (hookEvent.forceKeepAttr) {
continue;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+28 -49
View File
@@ -414,20 +414,15 @@
};
};
/**
* Resolve a set-valued configuration option.
*
* When `cfg` carries an own array property under `key`, build a fresh set
* from it - seeded with a clone of `base` when given, an empty set
* otherwise - using `transform` for case normalization. Otherwise return
* `fallback` as-is. Mirrors the long-standing inline pattern exactly: a
* fresh set object per parse on the array path, the (possibly shared)
* default on the fallback path.
* 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: case-transform function handed to addToSet;
* base: optional default set to merge the supplied values into
* @param options transform and optional base set to merge into
* @returns the resolved set
*/
const _resolveSetOption = function _resolveSetOption(cfg, key, fallback, options) {
@@ -976,9 +971,9 @@
if (parent.namespaceURI === HTML_NAMESPACE) {
return tagName === 'svg';
}
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
// The only way to switch from MathML to SVG is via <svg>
// if the parent is either <annotation-xml> or a MathML
// text integration point.
if (parent.namespaceURI === MATHML_NAMESPACE) {
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
}
@@ -1315,10 +1310,9 @@
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
};
/**
* Replace template expression syntax (mustache, ERB, template literal)
* with a space, in the same order the previous inline arrayForEach
* applied. One shared helper avoids re-allocating the expression array
* and iteration closure at every scrub site.
* Replace template expression syntax (mustache, ERB, template
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
* sites. Order matters: mustache, then ERB, then template literal.
*
* @param value the string to scrub
* @returns the scrubbed string
@@ -1473,14 +1467,10 @@
});
}
/**
* _isUnsafeNode
*
* Structural-threat checks that condemn a node regardless of the
* allowlists: mXSS via namespace confusion, risky CSS construction,
* processing instructions, and comments with markup-significant
* content. Pure predicate - reads only; the caller performs the
* removal. Check order is load-bearing and mirrors the original
* inline sequence.
* processing instructions, markup-bearing comments. Pure predicate;
* the caller removes. Check order is load-bearing.
*
* @param currentNode the node to inspect
* @param tagName the node's transformCaseFunc'd tag name
@@ -1506,15 +1496,11 @@
return false;
};
/**
* _sanitizeDisallowedNode
*
* Handles a node whose tag is not on the active allowlist (or is
* explicitly forbidden). Keeps allowed custom elements (returning
* false exits _sanitizeElements early, exactly as the previous
* inline code did - the namespace and fallback-tag checks and the
* afterSanitizeElements hook are intentionally skipped for kept
* custom elements). Otherwise hoists content per KEEP_CONTENT and
* removes the node.
* Handle a node whose tag is forbidden or not allowlisted: keep
* allowed custom elements (false return exits _sanitizeElements
* early - namespace/fallback checks and the afterSanitizeElements
* hook are intentionally skipped for kept custom elements), else
* hoist content per KEEP_CONTENT and remove.
*
* @param currentNode the disallowed node
* @param tagName the node's transformCaseFunc'd tag name
@@ -1691,12 +1677,10 @@
return !RESERVED_CUSTOM_ELEMENT_NAMES[stringToLowerCase(tagName)] && regExpTest(CUSTOM_ELEMENT$1, tagName);
};
/**
* _applyTrustedTypesToAttribute
*
* Wrap an attribute value in the matching Trusted Types object when
* the active policy and browser support require it. Namespaced
* attributes are returned unchanged (Trusted Types does not support
* them yet, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
* the active policy requires it. Namespaced attributes pass through
* unchanged (no TT support yet, see
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
*
* @param lcTag lowercase tag name of the containing element
* @param lcName lowercase attribute name
@@ -1720,17 +1704,12 @@
return value;
};
/**
* _setAttributeValue
*
* Write a (hook- or named-props-)modified attribute value back onto
* the element. On success, re-probe for clobbering introduced by the
* new value and remove the element when found; otherwise pop the
* removal entry recorded by the earlier _removeAttribute. On failure
* (e.g. an invalid data-* name for setAttributeNS), remove the
* attribute instead. Moved verbatim from the _sanitizeAttributes
* loop body - including the arrayPop bookkeeping, whose pairing with
* the SANITIZE_NAMED_PROPS _removeAttribute call above it is
* long-standing behavior.
* Write a modified attribute value back onto the element. On
* success, re-probe for clobbering introduced by the new value and
* remove the element when found; otherwise pop the removal entry
* recorded by the earlier _removeAttribute (long-standing pairing
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
* failure, remove the attribute instead.
*
* @param currentNode the element carrying the attribute
* @param name the attribute name as present on the element
@@ -1818,7 +1797,7 @@
_removeAttribute(name, currentNode);
continue;
}
/* Did the hooks approve of the attribute? */
/* Did the hooks force-keep the attribute? */
if (hookEvent.forceKeepAttr) {
continue;
}
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+28 -49
View File
@@ -136,20 +136,15 @@ const _createHooksMap = function (): HooksMap {
};
/**
* Resolve a set-valued configuration option.
*
* When `cfg` carries an own array property under `key`, build a fresh set
* from it - seeded with a clone of `base` when given, an empty set
* otherwise - using `transform` for case normalization. Otherwise return
* `fallback` as-is. Mirrors the long-standing inline pattern exactly: a
* fresh set object per parse on the array path, the (possibly shared)
* default on the fallback path.
* 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: case-transform function handed to addToSet;
* base: optional default set to merge the supplied values into
* @param options transform and optional base set to merge into
* @returns the resolved set
*/
const _resolveSetOption = function (
@@ -1015,9 +1010,9 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
return tagName === 'svg';
}
// The only way to switch from MathML to SVG is via`
// svg if parent is either <annotation-xml> or MathML
// text integration points.
// The only way to switch from MathML to SVG is via <svg>
// if the parent is either <annotation-xml> or a MathML
// text integration point.
if (parent.namespaceURI === MATHML_NAMESPACE) {
return (
tagName === 'svg' &&
@@ -1444,10 +1439,9 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
};
/**
* Replace template expression syntax (mustache, ERB, template literal)
* with a space, in the same order the previous inline arrayForEach
* applied. One shared helper avoids re-allocating the expression array
* and iteration closure at every scrub site.
* Replace template expression syntax (mustache, ERB, template
* literal) with a space; shared by all SAFE_FOR_TEMPLATES scrub
* sites. Order matters: mustache, then ERB, then template literal.
*
* @param value the string to scrub
* @returns the scrubbed string
@@ -1634,14 +1628,10 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
}
/**
* _isUnsafeNode
*
* Structural-threat checks that condemn a node regardless of the
* allowlists: mXSS via namespace confusion, risky CSS construction,
* processing instructions, and comments with markup-significant
* content. Pure predicate - reads only; the caller performs the
* removal. Check order is load-bearing and mirrors the original
* inline sequence.
* processing instructions, markup-bearing comments. Pure predicate;
* the caller removes. Check order is load-bearing.
*
* @param currentNode the node to inspect
* @param tagName the node's transformCaseFunc'd tag name
@@ -1687,15 +1677,11 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
};
/**
* _sanitizeDisallowedNode
*
* Handles a node whose tag is not on the active allowlist (or is
* explicitly forbidden). Keeps allowed custom elements (returning
* false exits _sanitizeElements early, exactly as the previous
* inline code did - the namespace and fallback-tag checks and the
* afterSanitizeElements hook are intentionally skipped for kept
* custom elements). Otherwise hoists content per KEEP_CONTENT and
* removes the node.
* Handle a node whose tag is forbidden or not allowlisted: keep
* allowed custom elements (false return exits _sanitizeElements
* early - namespace/fallback checks and the afterSanitizeElements
* hook are intentionally skipped for kept custom elements), else
* hoist content per KEEP_CONTENT and remove.
*
* @param currentNode the disallowed node
* @param tagName the node's transformCaseFunc'd tag name
@@ -1996,12 +1982,10 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
};
/**
* _applyTrustedTypesToAttribute
*
* Wrap an attribute value in the matching Trusted Types object when
* the active policy and browser support require it. Namespaced
* attributes are returned unchanged (Trusted Types does not support
* them yet, see https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
* the active policy requires it. Namespaced attributes pass through
* unchanged (no TT support yet, see
* https://bugs.chromium.org/p/chromium/issues/detail?id=1305293).
*
* @param lcTag lowercase tag name of the containing element
* @param lcName lowercase attribute name
@@ -2040,17 +2024,12 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
};
/**
* _setAttributeValue
*
* Write a (hook- or named-props-)modified attribute value back onto
* the element. On success, re-probe for clobbering introduced by the
* new value and remove the element when found; otherwise pop the
* removal entry recorded by the earlier _removeAttribute. On failure
* (e.g. an invalid data-* name for setAttributeNS), remove the
* attribute instead. Moved verbatim from the _sanitizeAttributes
* loop body - including the arrayPop bookkeeping, whose pairing with
* the SANITIZE_NAMED_PROPS _removeAttribute call above it is
* long-standing behavior.
* Write a modified attribute value back onto the element. On
* success, re-probe for clobbering introduced by the new value and
* remove the element when found; otherwise pop the removal entry
* recorded by the earlier _removeAttribute (long-standing pairing
* with the SANITIZE_NAMED_PROPS path - do not "fix" casually). On
* failure, remove the attribute instead.
*
* @param currentNode the element carrying the attribute
* @param name the attribute name as present on the element
@@ -2163,7 +2142,7 @@ function createDOMPurify(window: WindowLike = getGlobal()): DOMPurify {
continue;
}
/* Did the hooks approve of the attribute? */
/* Did the hooks force-keep the attribute? */
if (hookEvent.forceKeepAttr) {
continue;
}