mirror of
https://github.com/morluto/rea.git
synced 2026-11-11 01:13:39 +00:00
fix(javascript): report SVG script references and skipped bodies (#8627)
Co-authored-by: hhang <169027046+xu91102@users.noreply.github.com>
This commit is contained in:
1 parent
f95bdbce76
commit
20a282a181
4 files changed
+175
-6
No files matched your search
@@ -106,7 +106,7 @@ it.each([
|
||||
['<script language="vbscript">run()</script>', []],
|
||||
["<!-- <script>run();</script> -->", []],
|
||||
["<template><script>run();</script></template>", []],
|
||||
["<svg><script>run();</script></svg>", []],
|
||||
["<svg><script>run();</script></svg>", ["classic"]],
|
||||
])("classifies inline JavaScript in %s", (text, expected) => {
|
||||
expect(
|
||||
htmlArtifactReferences(text).inlineScripts.map(
|
||||
@@ -115,6 +115,65 @@ it.each([
|
||||
).toEqual(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['src="ignored.js"', "classic"],
|
||||
['language="vbscript"', "classic"],
|
||||
['xlink:type="simple"', "classic"],
|
||||
['type=" Text/JavaScript "', "classic"],
|
||||
['type="MODULE"', "module"],
|
||||
['type=" module "', null],
|
||||
['type="application/json"', null],
|
||||
['type="text/javascript; charset=utf-8"', null],
|
||||
['href="external.js"', null],
|
||||
['href=""', null],
|
||||
['xlink:href="external.js"', null],
|
||||
] as const)("classifies SVG inline bodies using %s", (attributes, expected) => {
|
||||
const result = htmlArtifactReferences(
|
||||
`<svg><script ${attributes}>run();</script></svg>`,
|
||||
);
|
||||
expect(result.inlineScripts.map(({ scriptType }) => scriptType)).toEqual(
|
||||
expected === null ? [] : [expected],
|
||||
);
|
||||
expect(result.inlineScriptCount).toBe(expected === null ? 0 : 1);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['href="external.js"', ["external.js"]],
|
||||
['xlink:href="external.js"', ["external.js"]],
|
||||
['href="first.js" xlink:href="second.js"', ["first.js"]],
|
||||
['xlink:href="second.js" href="first.js"', ["first.js"]],
|
||||
['href="" xlink:href="ignored.js"', []],
|
||||
['other:href="ignored.js"', []],
|
||||
['src="ignored.js"', []],
|
||||
])("extracts SVG script URL from %s", (attributes, paths) => {
|
||||
const result = htmlArtifactReferences(
|
||||
`<svg><script ${attributes}></script></svg>`,
|
||||
);
|
||||
expect(result.scripts.map(({ scriptPath }) => scriptPath)).toEqual(paths);
|
||||
expect(result.inlineScriptCount).toBe(0);
|
||||
});
|
||||
|
||||
it("shares precise body coordinates and location limits across HTML and SVG", () => {
|
||||
const prefix = '<!doctype html>\n<svg><script src="ignored.js">';
|
||||
const body = "\nrun();\n";
|
||||
expect(
|
||||
htmlArtifactReferences(`${prefix}${body}</script></svg>`).inlineScripts,
|
||||
).toEqual([
|
||||
{
|
||||
scriptType: "classic",
|
||||
location: { start: { line: 2, column: 30 }, end: { line: 4, column: 0 } },
|
||||
},
|
||||
]);
|
||||
const repeated = "<script>x</script><svg><script>y</script></svg>".repeat(60);
|
||||
const result = htmlArtifactReferences(repeated);
|
||||
expect(result.inlineScriptCount).toBe(120);
|
||||
expect(result.inlineScripts).toHaveLength(HTML_INLINE_SCRIPT_LOCATION_LIMIT);
|
||||
expect(
|
||||
htmlArtifactReferences("<template><svg><script>x</script></svg></template>")
|
||||
.inlineScriptCount,
|
||||
).toBe(0);
|
||||
});
|
||||
|
||||
it("retains exact inline script body coordinates, including unclosed scripts", () => {
|
||||
const prefix = "<!doctype html>\n<script>";
|
||||
const body = "\nfetch('/api');\n";
|
||||
|
||||
@@ -80,6 +80,30 @@ const javaScriptType = (
|
||||
const asciiLowercase = (value: string): string =>
|
||||
value.replace(/[A-Z]/gu, (letter) => letter.toLowerCase());
|
||||
|
||||
/** SVG uses href/XLink and has no HTML language fallback or src attribute. */
|
||||
const scriptAttributes = (node: DefaultTreeAdapterTypes.Element) => {
|
||||
const html = node.namespaceURI === "http://www.w3.org/1999/xhtml";
|
||||
const source = html
|
||||
? node.attrs.find(({ name }) => name === "src")
|
||||
: (node.attrs.find(
|
||||
({ name, namespace }) => name === "href" && namespace === undefined,
|
||||
) ??
|
||||
node.attrs.find(
|
||||
({ name, namespace }) =>
|
||||
name === "href" && namespace === "http://www.w3.org/1999/xlink",
|
||||
));
|
||||
return {
|
||||
source,
|
||||
scriptType: javaScriptType(
|
||||
html
|
||||
? node.attrs
|
||||
: node.attrs.filter(
|
||||
({ name, namespace }) => name === "type" && namespace === undefined,
|
||||
),
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract inert HTML script references, inline JavaScript bodies, and the
|
||||
* first document base href.
|
||||
@@ -105,15 +129,19 @@ export const htmlArtifactReferences = (
|
||||
if (node === undefined) continue;
|
||||
if (
|
||||
defaultTreeAdapter.isElementNode(node) &&
|
||||
node.namespaceURI === "http://www.w3.org/1999/xhtml"
|
||||
(node.namespaceURI === "http://www.w3.org/1999/xhtml" ||
|
||||
node.namespaceURI === "http://www.w3.org/2000/svg")
|
||||
) {
|
||||
if (node.tagName === "base" && baseHref === null) {
|
||||
if (
|
||||
node.namespaceURI === "http://www.w3.org/1999/xhtml" &&
|
||||
node.tagName === "base" &&
|
||||
baseHref === null
|
||||
) {
|
||||
const href = node.attrs.find(({ name }) => name === "href");
|
||||
if (href !== undefined) baseHref = href.value;
|
||||
}
|
||||
if (node.tagName === "script") {
|
||||
const scriptType = javaScriptType(node.attrs);
|
||||
const src = node.attrs.find(({ name }) => name === "src");
|
||||
const { scriptType, source: src } = scriptAttributes(node);
|
||||
const location = node.sourceCodeLocation?.startTag;
|
||||
if (src !== undefined && src.value !== "" && location !== undefined) {
|
||||
scripts.push({
|
||||
|
||||
Reference in new issue
Block a user