mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
fix(core,studio): escape user values in querySelector attribute selectors
Extract cssAttrSelector to packages/core/src/utils/cssSelector.ts and use it (or CSS.escape for browser-side code) at all 12 sites that previously interpolated raw user-authored values into querySelector attribute selectors. A " in a composition ID, script src, or data-start value would produce a malformed selector that throws. Node-side (core compiler/parser): uses the shared cssAttrSelector. Browser-side (runtime, studio): uses native CSS.escape(). Supersedes #1568 which fixed only the 3 bundler sites.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
import { describe, it, expect } from "bun:test";
|
||||
import { queryByAttr } from "./cssSelector";
|
||||
|
||||
function makeDoc(html: string) {
|
||||
const { parseHTML } = require("linkedom");
|
||||
const { document } = parseHTML(`<html><body>${html}</body></html>`);
|
||||
return document;
|
||||
}
|
||||
|
||||
describe("queryByAttr", () => {
|
||||
it("finds element by exact attribute match", () => {
|
||||
const doc = makeDoc('<div data-id="abc"></div>');
|
||||
const el = queryByAttr(doc, "data-id", "abc");
|
||||
expect(el).not.toBeNull();
|
||||
expect(el!.getAttribute("data-id")).toBe("abc");
|
||||
});
|
||||
|
||||
it("returns null when no match", () => {
|
||||
const doc = makeDoc('<div data-id="abc"></div>');
|
||||
expect(queryByAttr(doc, "data-id", "xyz")).toBeNull();
|
||||
});
|
||||
|
||||
it("handles values with double quotes", () => {
|
||||
const doc = makeDoc("<div></div>");
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", 'has"quote');
|
||||
expect(queryByAttr(doc, "data-id", 'has"quote')).toBe(el);
|
||||
});
|
||||
|
||||
it("handles values with backslashes", () => {
|
||||
const doc = makeDoc("<div></div>");
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", "has\\backslash");
|
||||
expect(queryByAttr(doc, "data-id", "has\\backslash")).toBe(el);
|
||||
});
|
||||
|
||||
it("handles values with closing bracket", () => {
|
||||
const doc = makeDoc("<div></div>");
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", "has]bracket");
|
||||
expect(queryByAttr(doc, "data-id", "has]bracket")).toBe(el);
|
||||
});
|
||||
|
||||
it("handles injection attempt", () => {
|
||||
const doc = makeDoc('<div data-id="safe"></div>');
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", '"][data-evil]');
|
||||
expect(queryByAttr(doc, "data-id", '"][data-evil]')).toBe(el);
|
||||
expect(queryByAttr(doc, "data-id", "safe")).toBeNull();
|
||||
});
|
||||
|
||||
it("filters by tag when provided", () => {
|
||||
const doc = makeDoc('<div data-src="a.js"></div><script data-src="a.js"></script>');
|
||||
const el = queryByAttr(doc, "data-src", "a.js", "script");
|
||||
expect(el).not.toBeNull();
|
||||
expect(el!.tagName.toLowerCase()).toBe("script");
|
||||
});
|
||||
|
||||
it("returns null when tag filter excludes match", () => {
|
||||
const doc = makeDoc('<div data-src="a.js"></div>');
|
||||
expect(queryByAttr(doc, "data-src", "a.js", "script")).toBeNull();
|
||||
});
|
||||
|
||||
it("handles values with newlines", () => {
|
||||
const doc = makeDoc("<div></div>");
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", "line1\nline2");
|
||||
expect(queryByAttr(doc, "data-id", "line1\nline2")).toBe(el);
|
||||
});
|
||||
|
||||
it("handles values with leading digits", () => {
|
||||
const doc = makeDoc("<div></div>");
|
||||
const el = doc.querySelector("div")!;
|
||||
el.setAttribute("data-id", "123abc");
|
||||
expect(queryByAttr(doc, "data-id", "123abc")).toBe(el);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user