fix(security): strip video embeds from rich text linkification
This commit is contained in:
@@ -687,6 +687,8 @@ export const updateLinks = (input) => {
|
||||
const EMPTY_QUILL_PARAGRAPHS_REGEX =
|
||||
/<p[^>]*>\s*(?:<br\s*\/?>| |\s)*\s*<\/p>/gi;
|
||||
|
||||
const VIDEO_EMBED_REGEX = /<(iframe|video)\b[^>]*>[\s\S]*?<\/\1\s*>/gi;
|
||||
|
||||
const URL_REGEX = /\b(https?:\/\/[^\s<]+)/gi;
|
||||
|
||||
const stripTrailingUrlPunctuation = (url) => {
|
||||
@@ -732,7 +734,9 @@ export const updateLinks = (input) => {
|
||||
const processHtmlString = (htmlString) => {
|
||||
if (typeof htmlString !== "string" || !htmlString) return htmlString;
|
||||
|
||||
let html = htmlString.replace(EMPTY_QUILL_PARAGRAPHS_REGEX, "");
|
||||
let html = htmlString
|
||||
.replace(VIDEO_EMBED_REGEX, "")
|
||||
.replace(EMPTY_QUILL_PARAGRAPHS_REGEX, "");
|
||||
|
||||
const anchorPlaceholders = [];
|
||||
html = html.replace(/<a\b[^>]*>[\s\S]*?<\/a>/gi, (match) => {
|
||||
|
||||
@@ -219,6 +219,83 @@ test("clients/fileRouteBuilder builds query with optional encoding and suffix he
|
||||
);
|
||||
});
|
||||
|
||||
const loadUpdateLinks = () => {
|
||||
const filePath = path.join(rootDir, "components", "utils", "index.js");
|
||||
const source = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
const start = source.indexOf("export const updateLinks =");
|
||||
const end = source.indexOf("\nexport const whichRepType", start);
|
||||
|
||||
assert.ok(start >= 0, "updateLinks export not found");
|
||||
assert.ok(end > start, "updateLinks boundary not found");
|
||||
|
||||
const implementation = source
|
||||
.slice(start, end)
|
||||
.replace("export const updateLinks =", "const updateLinks =");
|
||||
|
||||
const context = { module: { exports: {} } };
|
||||
|
||||
vm.runInNewContext(
|
||||
`${implementation}\nmodule.exports = updateLinks;`,
|
||||
context,
|
||||
{ filename: filePath }
|
||||
);
|
||||
|
||||
return context.module.exports;
|
||||
};
|
||||
|
||||
test("updateLinks removes Quill video iframe", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
const input =
|
||||
'<p>Before</p><iframe class="ql-video" src="https://example.com/video"></iframe><p>After</p>';
|
||||
|
||||
assert.strictEqual(updateLinks(input), "<p>Before</p><p>After</p>");
|
||||
});
|
||||
|
||||
test("updateLinks removes HTML video elements", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
const input =
|
||||
'<p>Before</p><video controls src="https://example.com/video"><source src="https://example.com/file.mp4"></video><p>After</p>';
|
||||
|
||||
assert.strictEqual(updateLinks(input), "<p>Before</p><p>After</p>");
|
||||
});
|
||||
|
||||
test("updateLinks converts plain URLs", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
assert.strictEqual(
|
||||
updateLinks("<p>Visit https://example.com.</p>"),
|
||||
'<p>Visit <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>.</p>'
|
||||
);
|
||||
});
|
||||
|
||||
test("updateLinks preserves existing anchors", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
const input = '<p><a href="https://example.com">Existing link</a></p>';
|
||||
|
||||
assert.strictEqual(updateLinks(input), input);
|
||||
});
|
||||
|
||||
test("updateLinks preserves standard rich-text formatting", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
const input = "<p><strong>Bold</strong> and <em>italic</em></p>";
|
||||
|
||||
assert.strictEqual(updateLinks(input), input);
|
||||
});
|
||||
|
||||
test("updateLinks removes empty Quill paragraphs", () => {
|
||||
const updateLinks = loadUpdateLinks();
|
||||
|
||||
assert.strictEqual(
|
||||
updateLinks("<p>Content</p><p><br></p><p> </p>"),
|
||||
"<p>Content</p>"
|
||||
);
|
||||
});
|
||||
|
||||
const run = async () => {
|
||||
let passed = 0;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user