Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
bda0b30
test(ooxml.js): cover base64 encode/decode boundaries and simplify de…
Mearman Sep 12, 2026
73f00ff
test(ooxml.js): cover buildXml's node kinds and drop unobservable bui…
Mearman Sep 12, 2026
aefe3f5
test(ooxml.js): cover parseXml's internal validation helpers directly
Mearman Sep 12, 2026
41b97a0
test(ooxml.js): cover isXmlNode's full truth table across every node …
Mearman Sep 12, 2026
7d00eaf
test(ooxml.js): cover looksLikeXml's BOM/whitespace skip and drop a r…
Mearman Sep 12, 2026
bfb4200
test(ooxml.js): cover every sniffed image signature and drop a redund…
Mearman Sep 12, 2026
6d2942f
test(ooxml.js): cover relsPathFor/resolveRelTarget's path arithmetic
Mearman Sep 12, 2026
5380441
test(ooxml.js): cover serial.ts's date/time boundaries and remove two…
Mearman Sep 12, 2026
ebd477f
test(ooxml.js): cover sqref parsing/formatting and simplify its white…
Mearman Sep 12, 2026
360a496
test(ooxml.js): cover captureResidualAttributes/residualAttributesFor…
Mearman Sep 12, 2026
bac112c
test(ooxml.js): cover loadSharedStrings and SharedStringTable directly
Mearman Sep 12, 2026
b4280b3
test(ooxml.js): prove readXlsx omits the definitions key when there a…
Mearman Sep 12, 2026
58cffd2
test(ooxml.js): cover readWorkbookDefinitions' relationship filtering…
Mearman Sep 12, 2026
2cf6143
test(ooxml.js): cover consecutive images with no candidate paragraph …
Mearman Sep 12, 2026
36411b7
test(ooxml.js): cover shading's "none" colour tokens and single-colou…
Mearman Sep 12, 2026
9ad53c9
test(ooxml.js): cover threaded-comment id formatting, counter increme…
Mearman Sep 12, 2026
2544b3b
test(ooxml.js): cover page-size tolerance's exact boundary and remove…
Mearman Sep 12, 2026
418f394
test(ooxml.js): cover numbering's overridden-level guard, namespace, …
Mearman Sep 12, 2026
9fad691
test(ooxml.js): cover flavour detection's own precondition directly
Mearman Sep 12, 2026
d5f6bff
fix(ooxml.js): populate the required displayText field on every test …
Mearman Sep 12, 2026
9a0bba0
refactor(ooxml.js): drop looksLikeSvg's redundant Math.min against by…
Mearman Sep 13, 2026
4e12e9c
test(ooxml.js): prove isXmlNode's element branch gates on type, not s…
Mearman Sep 13, 2026
d8392e2
test(ooxml.js): prove a reply's own counter increment never runs back…
Mearman Sep 13, 2026
d941286
test(ooxml.js): prove a table relationship is filtered by its own type
Mearman Sep 13, 2026
0dd7810
refactor(ooxml.js): drop isoDateTimeToSerial's redundant no-separator…
Mearman Sep 13, 2026
b623556
refactor(ooxml.js): drop parseSqref's redundant empty-token skip
Mearman Sep 13, 2026
b4260c6
refactor(ooxml.js): hoist buildXml's ignored pi/declaration child array
Mearman Sep 13, 2026
cecf442
test(ooxml.js): cover textContent's cdata concatenation, simplify rel…
Mearman Sep 13, 2026
85188bf
test(ooxml.js): cover buildCellShading's unrecognised-kind default br…
Mearman Sep 13, 2026
394a0f6
test(ooxml.js): cover figure-captions' image gate, join separator
Mearman Sep 13, 2026
bee91f7
test(ooxml.js): cover numbering's non-canonical ilvl/numId sort, unde…
Mearman Sep 13, 2026
d35a1a1
refactor(ooxml.js): drop bytesToBase64's redundant remainder-byte guards
Mearman Sep 13, 2026
5bd2d87
test(ooxml.js): cover embedded-object root-entry precedence, Package …
Mearman Sep 13, 2026
d908b86
fix(ooxml.js): correct a stale comment about the removed magic-byte gate
Mearman Sep 13, 2026
262bd6a
test(ooxml.js): add direct structural coverage for oleObjectBin
Mearman Sep 13, 2026
07a2412
test(ooxml.js): cover inherit's rel-type filter, placeholder fallback…
Mearman Sep 13, 2026
617e777
test(ooxml.js): assert italic is also undefined for an rPr with no attrs
Mearman Sep 13, 2026
bf4e275
test(ooxml.js): prove residualAttributesFor rejects a matching-first-…
Mearman Sep 13, 2026
98ff865
refactor(ooxml.js): rewrite oleObjectBin's fixed-array copy loops as …
Mearman Sep 13, 2026
04bb2d2
test(ooxml.js): add byte-level coverage for oleObjectBin's remaining …
Mearman Sep 13, 2026
b52598c
refactor(ooxml.js): drop oleObjectBin's three redundant zero-valued h…
Mearman Sep 13, 2026
a9ece7a
test(ooxml.js): cover defined-names' print-area/titles parse and buil…
Mearman Sep 13, 2026
fd07e1d
test(ooxml.js): cover data-validation's read/build attribute branches
Mearman Sep 13, 2026
6ea7ad1
test(ooxml.js): add direct coverage for the tree-walk/attr/rels helpers
Mearman Sep 13, 2026
cc85b32
test(ooxml.js): cover print-settings' margins, breaks, and fit/scale …
Mearman Sep 13, 2026
9f80912
refactor(ooxml.js): drop defined-names' redundant guards and regex re…
Mearman Sep 13, 2026
20f4930
test(ooxml.js): cover every ST_DataValidationOperator vocabulary member
Mearman Sep 13, 2026
8f84109
refactor(ooxml.js): drop print-settings' redundant scale-presence guard
Mearman Sep 13, 2026
9b59c32
test(ooxml.js): add direct structural coverage for chart cache reading
Mearman Sep 13, 2026
57cf4df
test(ooxml.js): add direct structural coverage for diagram text walking
Mearman Sep 13, 2026
7baa9b6
test(ooxml.js): prove a startOverride with no w:val leaves startAt alone
Mearman Sep 13, 2026
521354e
test(ooxml.js): distinguish a multi-level cache's last level from its…
Mearman Sep 13, 2026
8d43883
test(ooxml.js): prove an unrecognised paragraph child contributes no run
Mearman Sep 13, 2026
edc9732
refactor(ooxml.js): drop readToggle's redundant absent-value guard
Mearman Sep 13, 2026
074e078
test(ooxml.js): close style-cascade gaps in type discrimination and m…
Mearman Sep 13, 2026
a2a08e8
test(ooxml.js): prove an unrecognised asciiTheme resolves to no font
Mearman Sep 13, 2026
a54b472
refactor(ooxml.js): drop reading-order's provably redundant cut guards
Mearman Sep 13, 2026
ea49b67
test(ooxml.js): cover reading-order's axis-tie, recursion, and extent…
Mearman Sep 13, 2026
a46e6ec
test(ooxml.js): close reading-order's touching-boundary and gap-arith…
Mearman Sep 13, 2026
ec2429e
test(ooxml.js): add direct structural coverage for the embedded-fixtu…
Mearman Sep 13, 2026
5ae4645
test(ooxml.js): close metadata's blank-value, keyword-parsing, and pe…
Mearman Sep 13, 2026
4236147
test(ooxml.js): add direct structural coverage for xlsx table/name de…
Mearman Sep 13, 2026
4260890
test(ooxml.js): close color.ts's HSL boundary and gamma-threshold gaps
Mearman Sep 13, 2026
755af5c
test(ooxml.js): close xlsx.ts's rels-correlation and sheet-ordering gaps
Mearman Sep 13, 2026
105f306
test(ooxml.js): pick a non-coincidental (l, s) pair for the 1/6 hue b…
Mearman Sep 13, 2026
ffce1a9
test(ooxml.js): close drawingml's per-field, theme-fallback, and tran…
Mearman Sep 13, 2026
c6c4fd0
refactor(ooxml.js): drop localName's unreachable no-colon branch
Mearman Sep 13, 2026
20b0003
refactor(ooxml.js): drop applyGroupTransform's redundant identity sho…
Mearman Sep 13, 2026
96690e5
docs(ooxml.js): document canonicalizeGroupRotation's irreducible +180…
Mearman Sep 13, 2026
5098a3a
test(ooxml.js): cover isCompactXmlNode's full type-code truth table d…
Mearman Sep 14, 2026
f145b62
refactor(ooxml.js): drop comments' redundant presence guards before a…
Mearman Sep 14, 2026
7da5c3e
test(ooxml.js): close comments.ts's relationship-type, local-name, an…
Mearman Sep 14, 2026
18fea9b
test(ooxml.js): distinguish extentAlong's true earliest start from it…
Mearman Sep 14, 2026
26495ba
refactor(ooxml.js): drop constructs.ts's three redundant guards
Mearman Sep 14, 2026
dd248a7
test(ooxml.js): close constructs.ts's paragraph-index, checkbox, and …
Mearman Sep 14, 2026
af9f9ad
fix(ooxml.js): pin isBlockScopedHalf's own leading/trailing boundary …
Mearman Sep 14, 2026
62612ab
refactor(ooxml.js): drop drawings.ts's redundant column/row validity …
Mearman Sep 14, 2026
8f62f38
refactor(ooxml.js): drop drawings.ts's remaining redundant NaN-fallba…
Mearman Sep 14, 2026
4c13801
test(ooxml.js): cover SheetGridGeometry's column/row lookups and edit…
Mearman Sep 15, 2026
dbeee96
test(ooxml.js): close drawings.ts's remaining chart-frame and marker …
Mearman Sep 15, 2026
90a008f
test(ooxml.js): add direct structural coverage for buildDrawing and f…
Mearman Sep 15, 2026
aec479f
test(ooxml.js): close content.ts's row/column, span, and residue muta…
Mearman Sep 16, 2026
0b87069
test(ooxml.js): reach content.ts's genuine mutation ceiling
Mearman Sep 16, 2026
077247c
test(ooxml.js): close styles.ts's decoration key-presence and
Mearman Sep 16, 2026
94b56b5
test(ooxml.js): reach styles.ts's genuine mutation ceiling
Mearman Sep 16, 2026
c43c1d5
test(ooxml.js): fix two border-signature tests that could not
Mearman Sep 16, 2026
10f853d
test(ooxml.js): cover borderToXlsxStyle's double/dotted tokens
Mearman Sep 16, 2026
f8df73c
test(ooxml.js): add drawings-write.ts's own direct unit suite
Mearman Sep 16, 2026
7092fd5
test(ooxml.js): fix drawings-write test coverage attribution
Mearman Sep 16, 2026
3e9f28a
test(ooxml.js): close drawings-write's remaining chart-counter and sp…
Mearman Sep 16, 2026
59642da
test(ooxml.js): add conditional-format.ts's own direct unit suite
Mearman Sep 16, 2026
9f26268
test(ooxml.js): close conditional-format's operator, boundary, and re…
Mearman Sep 16, 2026
1c6cfdb
test(ooxml.js): cover pptx read's slide-size fallback, alignment, and…
Mearman Sep 16, 2026
e59d726
docs(ooxml.js): raise the mutation break threshold to the
Mearman Sep 16, 2026
ee8e81c
test(ooxml.js): cover build.ts's exact package-scaffolding output
Mearman Sep 16, 2026
01c3cf2
test(ooxml.js): cover build.ts's styles-part and docProps output exactly
Mearman Sep 16, 2026
dbca808
test(ooxml.js): cover build.ts's dimension, cols, row/cell assembly, …
Mearman Sep 16, 2026
b4ae2a8
test(ooxml.js): cover build.ts's page margins, page setup, and manual…
Mearman Sep 16, 2026
dc0552f
test(ooxml.js): cover build.ts's per-sheet table filtering and relati…
Mearman Sep 16, 2026
4eea4b6
test(ooxml.js): assert the derived Print_Area definedName's own text …
Mearman Sep 16, 2026
f8dcdf0
docs(ooxml.js): record build.ts's re-measured mutation score and a St…
Mearman Sep 16, 2026
75093a6
test(ooxml.js): fix build.test.ts's own type errors under Node typecheck
Mearman Sep 16, 2026
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
156 changes: 155 additions & 1 deletion packages/ooxml.js/src/compact.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, it } from "vitest";
import {
CompactXmlNodeSchema,
decodeCompactPackage,
decodePackage,
encodeCompactPackage,
Expand All @@ -8,7 +9,7 @@ import {
toCompact,
zipPackage,
} from "./index";
import type { Package, XmlElement } from "./index";
import type { CompactPackage, Package, XmlElement } from "./index";

function enc(s: string): Uint8Array<ArrayBuffer> {
return new TextEncoder().encode(s);
Expand Down Expand Up @@ -179,6 +180,109 @@ describe("compact size", () => {
});
});

describe("isCompactXmlNode (via CompactXmlNodeSchema)", () => {
it("rejects a non-array value", () => {
expect(CompactXmlNodeSchema.safeParse("nope").success).toBe(false);
expect(CompactXmlNodeSchema.safeParse({ 0: 1, 1: 0 }).success).toBe(false);
});

it("accepts a text/cdata/comment node ([1|2|3, number])", () => {
expect(CompactXmlNodeSchema.safeParse([1, 0]).success).toBe(true);
expect(CompactXmlNodeSchema.safeParse([2, 0]).success).toBe(true);
expect(CompactXmlNodeSchema.safeParse([3, 0]).success).toBe(true);
});

it("rejects a text/cdata/comment node with the wrong tuple length", () => {
expect(CompactXmlNodeSchema.safeParse([1, 0, 0]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([2, 0, 0]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([3, 0, 0]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([1]).success).toBe(false);
});

it("rejects a text/cdata/comment node whose value slot is not a number", () => {
expect(CompactXmlNodeSchema.safeParse([1, "x"]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([2, "x"]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([3, "x"]).success).toBe(false);
});

it("accepts a declaration node ([4, attrPairs])", () => {
expect(CompactXmlNodeSchema.safeParse([4, [0, 1]]).success).toBe(true);
expect(CompactXmlNodeSchema.safeParse([4, []]).success).toBe(true);
});

it("rejects a declaration node with the wrong tuple length", () => {
expect(CompactXmlNodeSchema.safeParse([4, [0, 1], 9]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([4]).success).toBe(false);
});

it("rejects a declaration node whose attr pairs are not a valid CompactAttrPairs", () => {
expect(CompactXmlNodeSchema.safeParse([4, "not-an-array"]).success).toBe(
false,
);
expect(CompactXmlNodeSchema.safeParse([4, [0, "x"]]).success).toBe(false);
});

it("accepts a pi node ([5, number, number])", () => {
expect(CompactXmlNodeSchema.safeParse([5, 0, 1]).success).toBe(true);
});

it("rejects a pi node with the wrong tuple length", () => {
expect(CompactXmlNodeSchema.safeParse([5, 0]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([5, 0, 1, 2]).success).toBe(false);
});

it("rejects a pi node whose target or content slot is not a number", () => {
expect(CompactXmlNodeSchema.safeParse([5, "x", 1]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([5, 0, "x"]).success).toBe(false);
});

it("accepts an element node ([0, tag, attrPairs, children])", () => {
expect(CompactXmlNodeSchema.safeParse([0, 0, [], []]).success).toBe(true);
expect(
CompactXmlNodeSchema.safeParse([0, 0, [1, 2], [[1, 0]]]).success,
).toBe(true);
});

it("rejects an element node with the wrong tuple length", () => {
expect(CompactXmlNodeSchema.safeParse([0, 0, [], [], 9]).success).toBe(
false,
);
expect(CompactXmlNodeSchema.safeParse([0, 0, []]).success).toBe(false);
});

it("rejects an element node whose tag slot is not a number", () => {
expect(CompactXmlNodeSchema.safeParse([0, "x", [], []]).success).toBe(
false,
);
});

it("rejects an element node whose attr pairs are not a valid CompactAttrPairs", () => {
expect(
CompactXmlNodeSchema.safeParse([0, 0, "not-an-array", []]).success,
).toBe(false);
expect(CompactXmlNodeSchema.safeParse([0, 0, [0, "x"], []]).success).toBe(
false,
);
});

it("rejects an element node whose children slot is not an array", () => {
expect(
CompactXmlNodeSchema.safeParse([0, 0, [], "not-an-array"]).success,
).toBe(false);
});

it("rejects an element node whose children are not all valid compact nodes", () => {
expect(
CompactXmlNodeSchema.safeParse([0, 0, [], [["not-a-node"]]]).success,
).toBe(false);
});

it("rejects an unrecognised leading type code, even one that happens to satisfy the element-shape checks", () => {
expect(CompactXmlNodeSchema.safeParse([9]).success).toBe(false);
expect(CompactXmlNodeSchema.safeParse([9, 0, [], []]).success).toBe(false);
});
});

describe("compact adversarial cases", () => {
it("round-trips an empty Package", () => {
const pkg: Package = { parts: {} };
Expand Down Expand Up @@ -217,6 +321,56 @@ describe("compact adversarial cases", () => {
expect(fromCompact(toCompact(pkg))).toEqual(pkg);
});

it("round-trips a cdata node", () => {
const pkg: Package = {
parts: {
"word/document.xml": {
kind: "xml",
nodes: [{ type: "cdata", value: "<raw> & unescaped" }],
},
},
};
expect(fromCompact(toCompact(pkg))).toEqual(pkg);
});

it("round-trips a processing-instruction node", () => {
const pkg: Package = {
parts: {
"word/document.xml": {
kind: "xml",
nodes: [
{
type: "pi",
target: "mso-application",
content: 'progid="Word.Document"',
},
],
},
},
};
expect(fromCompact(toCompact(pkg))).toEqual(pkg);
});

it("throws with the out-of-range string index when a string-table lookup fails", () => {
const cpkg: CompactPackage = {
s: [],
p: { "word/document.xml": [[1, 5]] },
};
expect(() => fromCompact(cpkg)).toThrow(
"fromCompact: string table index 5 is out of range",
);
});

it("throws when an attribute index-pairs array has odd length", () => {
const cpkg: CompactPackage = {
s: ["name-only"],
p: { "word/document.xml": [[4, [0]]] },
};
expect(() => fromCompact(cpkg)).toThrow(
"fromCompact: attribute index pairs array has odd length",
);
});

it("round-trips a large base64 binary part as a single interned string", () => {
const largeBase64 = Buffer.from(new Uint8Array(64 * 1024).fill(7)).toString(
"base64",
Expand Down
105 changes: 90 additions & 15 deletions packages/ooxml.js/src/image/sniff.test.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,102 @@
import { describe, expect, it } from "vitest";
import { sniffImageFormat } from "./sniff";

// Ported verbatim from documents.js's src/image/sniff.test.ts.
describe("sniffImageFormat", () => {
it("recognises a PNG signature", () => {
expect(
sniffImageFormat(
new Uint8Array([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0, 0]),
),
).toBe("png");
function enc(s: string): number[] {
return Array.from(new TextEncoder().encode(s));
}

describe("sniffImageFormat: PNG", () => {
it("detects a genuine PNG signature", () => {
const bytes = new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 1, 2, 3,
]);
expect(sniffImageFormat(bytes)).toBe("png");
});

it("does not match a truncated PNG signature (shorter than the real one)", () => {
const bytes = new Uint8Array([0x89, 0x50, 0x4e, 0x47]);
expect(sniffImageFormat(bytes)).toBeUndefined();
});

it("does not match bytes that agree with the PNG signature's prefix but diverge partway through", () => {
const bytes = new Uint8Array([
0x89, 0x50, 0x4e, 0x47, 0x00, 0x0a, 0x1a, 0x0a,
]);
expect(sniffImageFormat(bytes)).toBeUndefined();
});
});

describe("sniffImageFormat: JPEG", () => {
it("detects a genuine JPEG signature", () => {
expect(sniffImageFormat(new Uint8Array([0xff, 0xd8, 0xff, 0xe0]))).toBe(
"jpeg",
);
});

it("recognises a JPEG signature", () => {
it("does not match a signature that diverges on the final byte", () => {
expect(
sniffImageFormat(new Uint8Array([0xff, 0xd8, 0xff, 0xe0, 0, 0])),
).toBe("jpeg");
sniffImageFormat(new Uint8Array([0xff, 0xd8, 0x00])),
).toBeUndefined();
});
});

describe("sniffImageFormat: GIF", () => {
it("detects the GIF87a signature", () => {
const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x37, 0x61, 1, 2]);
expect(sniffImageFormat(bytes)).toBe("gif");
});

it("detects the GIF89a signature", () => {
const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 1, 2]);
expect(sniffImageFormat(bytes)).toBe("gif");
});

it("returns undefined for unrecognised bytes", () => {
expect(sniffImageFormat(new Uint8Array([1, 2, 3, 4]))).toBeUndefined();
it("does not match a GIF-like prefix that diverges on the version byte", () => {
const bytes = new Uint8Array([0x47, 0x49, 0x46, 0x38, 0x30, 0x61]);
expect(sniffImageFormat(bytes)).toBeUndefined();
});
});

describe("sniffImageFormat: SVG", () => {
it("detects an SVG that opens directly with the root <svg> tag", () => {
const bytes = new Uint8Array(enc('<svg xmlns="x"><path/></svg>'));
expect(sniffImageFormat(bytes)).toBe("svg");
});

it("detects an SVG whose root tag is preceded by an XML prolog", () => {
const bytes = new Uint8Array(
enc('<?xml version="1.0"?><svg xmlns="x"></svg>'),
);
expect(sniffImageFormat(bytes)).toBe("svg");
});

it("detects an SVG whose root/prolog is preceded by leading whitespace", () => {
const bytes = new Uint8Array(enc(' \n\t<svg xmlns="x"></svg>'));
expect(sniffImageFormat(bytes)).toBe("svg");
});

it("does not detect an SVG signature in plain, unrelated text", () => {
const bytes = new Uint8Array(enc("just some text, not a document"));
expect(sniffImageFormat(bytes)).toBeUndefined();
});

it("does not detect an SVG signature in an empty byte array", () => {
expect(sniffImageFormat(new Uint8Array([]))).toBeUndefined();
});

it("only sniffs the leading 1024-byte window, never a '<svg' tag that appears only later in a longer document", () => {
// 2000 bytes of non-SVG filler, with a real '<svg' root tag starting well past the 1024-byte sniff window: the real function must never find it there.
const filler = new Uint8Array(2000).fill(0x2e); // '.'
const svgTail = enc("<svg xmlns='x'></svg>");
const bytes = new Uint8Array(2000 + svgTail.length);
bytes.set(filler, 0);
bytes.set(svgTail, 1500);
expect(sniffImageFormat(bytes)).toBeUndefined();
});
});

it("returns undefined for bytes shorter than the shortest signature", () => {
expect(sniffImageFormat(new Uint8Array([0xff, 0xd8]))).toBeUndefined();
describe("sniffImageFormat: no format recognised", () => {
it("returns undefined for bytes matching none of the known signatures", () => {
expect(sniffImageFormat(new Uint8Array([1, 2, 3, 4, 5]))).toBeUndefined();
});
});
7 changes: 3 additions & 4 deletions packages/ooxml.js/src/image/sniff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,11 @@ const GIF89A_SIGNATURE: readonly number[] = [
0x47, 0x49, 0x46, 0x38, 0x39, 0x61,
];

// No separate length guard needed: bytes[i] is `undefined` for any index at or past bytes.length (an out-of-range read never throws), and undefined can never equal a real signature byte value -- so bytes shorter than the signature already fail this loop's own comparison at the first index past their own end.
function startsWith(
bytes: Uint8Array<ArrayBuffer>,
signature: readonly number[],
): boolean {
if (bytes.length < signature.length) {
return false;
}
for (let i = 0; i < signature.length; i++) {
if (bytes[i] !== signature[i]) {
return false;
Expand All @@ -31,7 +29,8 @@ function startsWith(
const SVG_SNIFF_WINDOW = 1024;

function looksLikeSvg(bytes: Uint8Array<ArrayBuffer>): boolean {
const window = bytes.subarray(0, Math.min(bytes.length, SVG_SNIFF_WINDOW));
// No Math.min against bytes.length needed: subarray's own end argument is clamped to the array's length regardless of what is asked for, so requesting SVG_SNIFF_WINDOW bytes from a shorter buffer already yields only the bytes that exist.
const window = bytes.subarray(0, SVG_SNIFF_WINDOW);
let text = "";
for (const byte of window) {
text += String.fromCharCode(byte);
Expand Down
Loading
Loading