Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: prevent auto closing valid xml in svg #4699

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions apps/builder/app/builder/features/settings-panel/controls/code.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,30 +67,58 @@ const ErrorInfo = ({

type Error = { message: string; value: string; expected: string };

/**
* Use DOMParser in xml mode to parse potential svg
*/
const parseSvg = (value: string) => {
const doc = new DOMParser().parseFromString(value, "application/xml");
const errorNode = doc.querySelector("parsererror");
if (errorNode) {
return "";
}
return doc.documentElement.outerHTML;
};

const parseHtml = (value: string) => {
const div = document.createElement("div");
div.innerHTML = value;
return div.innerHTML;
};

// The problem is to identify broken HTML and because browser is flexible and always tries to fix it we never
// know if something is actually broken.
// 1. Parse the HTML using DOM
// 2. Get HTML via innerHTML
// 3. Compare the original HTML with innerHTML
// 4. We try to minimize the amount of false positives by removing
// 1. Parse potential SVG with XML parser and serialize
// 2. Compare the original SVG with resulting value
// 3. Parse the HTML using DOM parser and serialize
// 4. Compare the original HTML with resulting value
// 5. We try to minimize the amount of false positives by removing
// - different amount of whitespace
// - unifying `boolean=""` is the same as `boolean`
// - xmlns attirbute which is always reordered first
const validateHtml = (value: string): Error | undefined => {
const div = document.createElement("div");
div.innerHTML = value;
const expected = div.innerHTML;
const clean = (value: string) => {
return (
value
// Compare without whitespace to avoid false positives
.replace(/\s/g, "")
.replaceAll(/\s/g, "")
// normalize boolean attributes by turning `boolean=""` into `boolean`
.replace('=""', "")
.replaceAll('=""', "")
// namespace attribute is always reordered first
.replaceAll('xmlns="http://www.w3.org/2000/svg"', "")
);
};
if (clean(value) !== clean(expected)) {
return { message: "Invalid HTML detected", value, expected };
// in many cases svg is valid xml so serialize in xml mode first
// to avoid false positive of auto closing svg tags, for example
// <path /> -> <path></path>
const xml = parseSvg(value);
if (clean(xml) === clean(value)) {
return;
}
const html = parseHtml(value);
if (clean(html) === clean(value)) {
return;
}
return { message: "Invalid HTML detected", value, expected: html ?? "" };
};

export const CodeControl = ({
Expand Down
Loading