113 lines
3.6 KiB
TypeScript
113 lines
3.6 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import {
|
|
defaultSvgLimits,
|
|
utf8ByteLength,
|
|
type SvgResourceLimits,
|
|
} from "../../src/app/limits";
|
|
import { parseSvgSource } from "../../src/document/source-parser";
|
|
|
|
const mutableLimits = defaultSvgLimits as SvgResourceLimits;
|
|
let original: SvgResourceLimits;
|
|
|
|
beforeEach(() => {
|
|
original = { ...defaultSvgLimits };
|
|
Object.assign(mutableLimits, {
|
|
maximumAttributeLength: 30,
|
|
maximumPathCommandsPerPath: 2,
|
|
maximumPathCommandsTotal: 3,
|
|
maximumCssRules: 1,
|
|
maximumReferences: 1,
|
|
maximumAnimations: 1,
|
|
maximumFilterPrimitives: 1,
|
|
maximumTextLength: 3,
|
|
maximumEmbeddedResourceBytes: 10,
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
Object.assign(mutableLimits, original);
|
|
});
|
|
|
|
describe("configured SVG resource limits", () => {
|
|
it("counts UTF-8 bytes without allocating an encoded copy", () => {
|
|
expect(utf8ByteLength("Aé🙂\ud800")).toBe(10);
|
|
});
|
|
|
|
it("rejects a hard-limit document before XML or semantic processing", () => {
|
|
const result = parseSvgSource(
|
|
'<svg xmlns="http://www.w3.org/2000/svg"/>',
|
|
0,
|
|
{ ...original, sourceHardBytes: 8, sourceSoftBytes: 4 },
|
|
);
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.semantic).toBeNull();
|
|
expect(result.diagnostics).toEqual([
|
|
expect.objectContaining({ code: "source-hard-limit" }),
|
|
]);
|
|
});
|
|
|
|
it("reports every bounded resource class with source-preserving diagnostics", () => {
|
|
const result = parseSvgSource(
|
|
`<svg xmlns="http://www.w3.org/2000/svg" data-long="1234567890123456789012345678901">
|
|
<style>.a{} .b{}</style>
|
|
<defs><filter id="f"><feBlend/><feGaussianBlur/></filter></defs>
|
|
<path d="M0 0L1 1L2 2L3 3" fill="url(#a)" stroke="url(#b)"/>
|
|
<animate attributeName="opacity"/><set attributeName="fill"/>
|
|
<text>long</text>
|
|
<image href="data:image/png;base64,abcdef"/>
|
|
</svg>`,
|
|
1,
|
|
);
|
|
const codes = result.diagnostics.map((diagnostic) => diagnostic.code);
|
|
|
|
expect(result.valid).toBe(false);
|
|
expect(result.semantic).not.toBeNull();
|
|
expect(codes).toEqual(
|
|
expect.arrayContaining([
|
|
"attribute-length-limit",
|
|
"path-command-per-element-limit",
|
|
"path-command-limit",
|
|
"css-rule-limit",
|
|
"reference-limit",
|
|
"animation-limit",
|
|
"filter-primitive-limit",
|
|
"text-length-limit",
|
|
"total-text-limit",
|
|
"embedded-resource-limit",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("does not count exponent markers as path commands", () => {
|
|
Object.assign(mutableLimits, {
|
|
maximumPathCommandsPerPath: 10,
|
|
maximumPathCommandsTotal: 10,
|
|
maximumTextLength: original.maximumTextLength,
|
|
maximumAttributeLength: original.maximumAttributeLength,
|
|
});
|
|
const result = parseSvgSource(
|
|
'<svg xmlns="http://www.w3.org/2000/svg"><path d="M1e2 1e-2L3e1 4e0"/></svg>',
|
|
1,
|
|
);
|
|
expect(result.semantic?.metrics.pathCommandCount).toBe(2);
|
|
});
|
|
|
|
it("bounds aggregate embedded data even when each resource is below the limit", () => {
|
|
const perResource = "data:image/png;base64,aaaaaaaaaaaaaaaaaaaaaaaa";
|
|
const result = parseSvgSource(
|
|
`<svg xmlns="http://www.w3.org/2000/svg"><image href="${perResource}"/><image href="${perResource}"/></svg>`,
|
|
2,
|
|
{
|
|
...original,
|
|
maximumEmbeddedResourceBytes: 60,
|
|
},
|
|
);
|
|
const codes = result.diagnostics.map((diagnostic) => diagnostic.code);
|
|
|
|
expect(codes).not.toContain("embedded-resource-limit");
|
|
expect(codes).toContain("embedded-resource-total-limit");
|
|
expect(result.valid).toBe(false);
|
|
});
|
|
});
|