Skip to content

Commit 7d8c5c3

Browse files
authored
fix(plist): only read direct-child keys when parsing a dict (#295)
getElementsByTagName('key') is recursive, so keys from nested dicts leaked into the parent object and a nested key could overwrite a parent key of the same name. Walk direct children instead and skip past each parsed value.
1 parent 2c92ef9 commit 7d8c5c3

2 files changed

Lines changed: 80 additions & 4 deletions

File tree

src/lib/plist/plist-parser.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,21 +111,32 @@ export function parsePlist(xmlData: string | Buffer): PlistDictionary {
111111

112112
/**
113113
* Parse a plist `<dict>` element into a JavaScript object.
114+
* Only direct-child `<key>` elements are considered: getElementsByTagName
115+
* is recursive and would flatten keys of nested dicts into the parent.
114116
*/
115117
function parseDict(dictNode: Element): PlistDictionary {
116118
const obj: PlistDictionary = {};
117-
const keys = dictNode.getElementsByTagName('key');
119+
let childNode = dictNode.firstChild;
118120

119-
for (let i = 0; i < keys.length; i++) {
120-
const keyName = keys[i].textContent || '';
121-
let valueNode = keys[i].nextSibling;
121+
while (childNode) {
122+
const keyNode = childNode;
123+
childNode = childNode.nextSibling;
124+
125+
if (keyNode.nodeType !== Node.ELEMENT_NODE || keyNode.nodeName !== 'key') {
126+
continue;
127+
}
128+
129+
const keyName = keyNode.textContent || '';
130+
let valueNode = keyNode.nextSibling;
122131

123132
while (valueNode && valueNode.nodeType !== Node.ELEMENT_NODE) {
124133
valueNode = valueNode.nextSibling;
125134
}
126135

127136
if (valueNode) {
128137
obj[keyName] = parseNode(valueNode as Element);
138+
// Skip ahead of the parsed value so the loop doesn't re-visit it
139+
childNode = valueNode.nextSibling;
129140
}
130141
}
131142

test/unit/plist/plist-parser.spec.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,71 @@ describe('Plist Parser', function () {
133133

134134
const level2 = level1.level2 as PlistDictionary;
135135
assert.strictEqual(level2.level3, 'deep value');
136+
137+
// Nested keys must not leak into ancestor dicts
138+
assert.deepStrictEqual(Object.keys(result), ['level1']);
139+
assert.deepStrictEqual(Object.keys(level1), ['level2']);
140+
assert.deepStrictEqual(Object.keys(level2), ['level3']);
141+
});
142+
143+
it('should not let a nested key overwrite an outer key with the same name', function () {
144+
const xml = `
145+
<?xml version="1.0" encoding="UTF-8"?>
146+
<plist version="1.0">
147+
<dict>
148+
<key>Result</key>
149+
<string>ok</string>
150+
<key>Nested</key>
151+
<dict>
152+
<key>Result</key>
153+
<integer>1</integer>
154+
<key>Inner</key>
155+
<string>x</string>
156+
</dict>
157+
<key>Name</key>
158+
<string>dev</string>
159+
</dict>
160+
</plist>
161+
`;
162+
163+
const result = parseXmlPlist(xml);
164+
assert.strictEqual(result.Result, 'ok');
165+
assert.strictEqual(result.Name, 'dev');
166+
assert.ok(!('Inner' in result));
167+
168+
const nested = result.Nested as PlistDictionary;
169+
assert.strictEqual(nested.Result, 1);
170+
assert.strictEqual(nested.Inner, 'x');
171+
});
172+
173+
it('should not leak keys from dicts nested inside arrays', function () {
174+
const xml = `
175+
<?xml version="1.0" encoding="UTF-8"?>
176+
<plist version="1.0">
177+
<dict>
178+
<key>Status</key>
179+
<string>Complete</string>
180+
<key>Items</key>
181+
<array>
182+
<dict>
183+
<key>Status</key>
184+
<string>Pending</string>
185+
<key>Id</key>
186+
<integer>7</integer>
187+
</dict>
188+
</array>
189+
</dict>
190+
</plist>
191+
`;
192+
193+
const result = parseXmlPlist(xml);
194+
assert.strictEqual(result.Status, 'Complete');
195+
assert.deepStrictEqual(Object.keys(result), ['Status', 'Items']);
196+
197+
const items = result.Items as PlistArray;
198+
const item = items[0] as PlistDictionary;
199+
assert.strictEqual(item.Status, 'Pending');
200+
assert.strictEqual(item.Id, 7);
136201
});
137202

138203
it('should parse mixed arrays and dictionaries', function () {

0 commit comments

Comments
 (0)