|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { DOMParser } = require('@xmldom/xmldom'); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
exports.parse = parse; |
|
|
|
|
|
var TEXT_NODE = 3; |
|
|
var CDATA_NODE = 4; |
|
|
var COMMENT_NODE = 8; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function shouldIgnoreNode (node) { |
|
|
return node.nodeType === TEXT_NODE |
|
|
|| node.nodeType === COMMENT_NODE |
|
|
|| node.nodeType === CDATA_NODE; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isEmptyNode(node){ |
|
|
if(!node.childNodes || node.childNodes.length === 0) { |
|
|
return true; |
|
|
} else { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
function invariant(test, message) { |
|
|
if (!test) { |
|
|
throw new Error(message); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parse (xml) { |
|
|
var doc = new DOMParser().parseFromString(xml); |
|
|
invariant( |
|
|
doc.documentElement.nodeName === 'plist', |
|
|
'malformed document. First element should be <plist>' |
|
|
); |
|
|
var plist = parsePlistXML(doc.documentElement); |
|
|
|
|
|
|
|
|
|
|
|
if (plist.length == 1) plist = plist[0]; |
|
|
|
|
|
return plist; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parsePlistXML (node) { |
|
|
var i, new_obj, key, val, new_arr, res, counter, type; |
|
|
|
|
|
if (!node) |
|
|
return null; |
|
|
|
|
|
if (node.nodeName === 'plist') { |
|
|
new_arr = []; |
|
|
if (isEmptyNode(node)) { |
|
|
return new_arr; |
|
|
} |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
if (!shouldIgnoreNode(node.childNodes[i])) { |
|
|
new_arr.push( parsePlistXML(node.childNodes[i])); |
|
|
} |
|
|
} |
|
|
return new_arr; |
|
|
} else if (node.nodeName === 'dict') { |
|
|
new_obj = {}; |
|
|
key = null; |
|
|
counter = 0; |
|
|
if (isEmptyNode(node)) { |
|
|
return new_obj; |
|
|
} |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
if (shouldIgnoreNode(node.childNodes[i])) continue; |
|
|
if (counter % 2 === 0) { |
|
|
invariant( |
|
|
node.childNodes[i].nodeName === 'key', |
|
|
'Missing key while parsing <dict/>.' |
|
|
); |
|
|
key = parsePlistXML(node.childNodes[i]); |
|
|
} else { |
|
|
invariant( |
|
|
node.childNodes[i].nodeName !== 'key', |
|
|
'Unexpected key "' |
|
|
+ parsePlistXML(node.childNodes[i]) |
|
|
+ '" while parsing <dict/>.' |
|
|
); |
|
|
new_obj[key] = parsePlistXML(node.childNodes[i]); |
|
|
} |
|
|
counter += 1; |
|
|
} |
|
|
if (counter % 2 === 1) { |
|
|
new_obj[key] = ''; |
|
|
} |
|
|
|
|
|
return new_obj; |
|
|
|
|
|
} else if (node.nodeName === 'array') { |
|
|
new_arr = []; |
|
|
if (isEmptyNode(node)) { |
|
|
return new_arr; |
|
|
} |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
if (!shouldIgnoreNode(node.childNodes[i])) { |
|
|
res = parsePlistXML(node.childNodes[i]); |
|
|
if (null != res) new_arr.push(res); |
|
|
} |
|
|
} |
|
|
return new_arr; |
|
|
|
|
|
} else if (node.nodeName === '#text') { |
|
|
|
|
|
|
|
|
} else if (node.nodeName === 'key') { |
|
|
if (isEmptyNode(node)) { |
|
|
return ''; |
|
|
} |
|
|
|
|
|
invariant( |
|
|
node.childNodes[0].nodeValue !== '__proto__', |
|
|
'__proto__ keys can lead to prototype pollution. More details on CVE-2022-22912' |
|
|
); |
|
|
|
|
|
return node.childNodes[0].nodeValue; |
|
|
} else if (node.nodeName === 'string') { |
|
|
res = ''; |
|
|
if (isEmptyNode(node)) { |
|
|
return res; |
|
|
} |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
var type = node.childNodes[i].nodeType; |
|
|
if (type === TEXT_NODE || type === CDATA_NODE) { |
|
|
res += node.childNodes[i].nodeValue; |
|
|
} |
|
|
} |
|
|
return res; |
|
|
|
|
|
} else if (node.nodeName === 'integer') { |
|
|
invariant( |
|
|
!isEmptyNode(node), |
|
|
'Cannot parse "" as integer.' |
|
|
); |
|
|
return parseInt(node.childNodes[0].nodeValue, 10); |
|
|
|
|
|
} else if (node.nodeName === 'real') { |
|
|
invariant( |
|
|
!isEmptyNode(node), |
|
|
'Cannot parse "" as real.' |
|
|
); |
|
|
res = ''; |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
if (node.childNodes[i].nodeType === TEXT_NODE) { |
|
|
res += node.childNodes[i].nodeValue; |
|
|
} |
|
|
} |
|
|
return parseFloat(res); |
|
|
|
|
|
} else if (node.nodeName === 'data') { |
|
|
res = ''; |
|
|
if (isEmptyNode(node)) { |
|
|
return Buffer.from(res, 'base64'); |
|
|
} |
|
|
for (i=0; i < node.childNodes.length; i++) { |
|
|
if (node.childNodes[i].nodeType === TEXT_NODE) { |
|
|
res += node.childNodes[i].nodeValue.replace(/\s+/g, ''); |
|
|
} |
|
|
} |
|
|
return Buffer.from(res, 'base64'); |
|
|
|
|
|
} else if (node.nodeName === 'date') { |
|
|
invariant( |
|
|
!isEmptyNode(node), |
|
|
'Cannot parse "" as Date.' |
|
|
) |
|
|
return new Date(node.childNodes[0].nodeValue); |
|
|
|
|
|
} else if (node.nodeName === 'null') { |
|
|
return null; |
|
|
|
|
|
} else if (node.nodeName === 'true') { |
|
|
return true; |
|
|
|
|
|
} else if (node.nodeName === 'false') { |
|
|
return false; |
|
|
} else { |
|
|
throw new Error('Invalid PLIST tag ' + node.nodeName); |
|
|
} |
|
|
} |
|
|
|