Skip to content

Commit 46784f2

Browse files
authored
fix: internalize xml parsing (#606)
1 parent 5083d04 commit 46784f2

4 files changed

Lines changed: 429 additions & 92 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@
204204
"detox"
205205
],
206206
"dependencies": {
207-
"fast-xml-parser": "^5.7.2",
208207
"pngjs": "^7.0.0",
209208
"yaml": "^2.9.0"
210209
},

pnpm-lock.yaml

Lines changed: 0 additions & 37 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import assert from 'node:assert/strict';
2+
import { test } from 'vitest';
3+
4+
import { parseXmlDocumentSync } from '../xml.ts';
5+
6+
test('parseXmlDocumentSync preserves ordered nodes with attributes and decoded text', () => {
7+
const nodes = parseXmlDocumentSync(
8+
[
9+
'<?xml version="1.0" encoding="UTF-8"?>',
10+
'<plist version="1.0">',
11+
'<dict>',
12+
'<key>CFBundleDisplayName</key>',
13+
'<string escaped="&quot;yes&quot;">Example &amp; App</string>',
14+
'<empty enabled="true"/>',
15+
'</dict>',
16+
'</plist>',
17+
].join(''),
18+
);
19+
20+
assert.deepEqual(nodes, [
21+
{
22+
name: 'plist',
23+
attributes: { version: '1.0' },
24+
text: null,
25+
children: [
26+
{
27+
name: 'dict',
28+
attributes: {},
29+
text: null,
30+
children: [
31+
{ name: 'key', attributes: {}, text: 'CFBundleDisplayName', children: [] },
32+
{
33+
name: 'string',
34+
attributes: { escaped: '"yes"' },
35+
text: 'Example & App',
36+
children: [],
37+
},
38+
{ name: 'empty', attributes: { enabled: 'true' }, text: null, children: [] },
39+
],
40+
},
41+
],
42+
},
43+
]);
44+
});
45+
46+
test('parseXmlDocumentSync reads cdata text and skips declarations', () => {
47+
const nodes = parseXmlDocumentSync(
48+
[
49+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
50+
'<root>',
51+
'<!-- ignored -->',
52+
'<value><![CDATA[ <raw>&text</raw> ]]></value>',
53+
'</root>',
54+
].join(''),
55+
);
56+
57+
assert.equal(nodes[0]?.children[0]?.text, '<raw>&text</raw>');
58+
});
59+
60+
test('parseXmlDocumentSync skips UTF-8 byte order marks', () => {
61+
const nodes = parseXmlDocumentSync('\uFEFF<root/>');
62+
63+
assert.equal(nodes[0]?.name, 'root');
64+
});
65+
66+
test('parseXmlDocumentSync rejects mismatched closing tags', () => {
67+
assert.throws(() => parseXmlDocumentSync('<root><child></root>'), /Expected <\/child>/);
68+
});
69+
70+
test('parseXmlDocumentSync does not expand custom doctype entities', () => {
71+
const nodes = parseXmlDocumentSync(
72+
'<!DOCTYPE root [<!ENTITY secret "expanded">]><root>&secret;</root>',
73+
);
74+
75+
assert.equal(nodes[0]?.text, '&secret;');
76+
});
77+
78+
test('parseXmlDocumentSync rejects unsafe attribute names', () => {
79+
for (const attributeName of [
80+
'__defineGetter__',
81+
'__defineSetter__',
82+
'__proto__',
83+
'constructor',
84+
'prototype',
85+
]) {
86+
assert.throws(
87+
() => parseXmlDocumentSync(`<root ${attributeName}="polluted"/>`),
88+
new RegExp(`Unsupported XML attribute name "${attributeName}"`),
89+
);
90+
}
91+
});
92+
93+
test('parseXmlDocumentSync rejects excessive nesting depth', () => {
94+
const xml = `${'<node>'.repeat(257)}${'</node>'.repeat(257)}`;
95+
96+
assert.throws(() => parseXmlDocumentSync(xml), /Maximum XML nesting depth/);
97+
});
98+
99+
test('parseXmlDocumentSync rejects documents above the configured size limit', () => {
100+
assert.throws(
101+
() => parseXmlDocumentSync('<root>oversized</root>', { maxDocumentChars: 10 }),
102+
/XML document exceeds maximum supported size of 10 characters/,
103+
);
104+
});

0 commit comments

Comments
 (0)