/**
* Module dependencies.
*/
const { DOMParser } = require("@xmldom/xmldom");
/**
* Module exports.
*/
exports.parse = parse;
var TEXT_NODE = 3;
var CDATA_NODE = 4;
var COMMENT_NODE = 8;
/**
* We ignore raw text (usually whitespace), ,
* and raw CDATA nodes.
*
* @param {Element} node
* @returns {Boolean}
* @api private
*/
function shouldIgnoreNode(node) {
return (
node.nodeType === TEXT_NODE ||
node.nodeType === COMMENT_NODE ||
node.nodeType === CDATA_NODE
);
}
/**
* Check if the node is empty. Some plist file has such node:
*
* this node shoud be ignored.
*
* @see https://github.com/TooTallNate/plist.js/issues/66
* @param {Element} node
* @returns {Boolean}
* @api private
*/
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);
}
}
/**
* Parses a Plist XML string. Returns an Object.
*
* @param {String} xml - the XML String to decode
* @returns {Mixed} the decoded value from the Plist XML
* @api public
*/
function parse(xml) {
var doc = new DOMParser().parseFromString(xml, "text/xml");
invariant(
doc.documentElement.nodeName === "plist",
"malformed document. First element should be ",
);
var plist = parsePlistXML(doc.documentElement);
// the root node gets interpreted as an Array,
// so pull out the inner data first
if (plist.length == 1) plist = plist[0];
return plist;
}
/**
* Convert an XML based plist document into a JSON representation.
*
* @param {Object} xml_node - current XML node in the plist
* @returns {Mixed} built up JSON object
* @api private
*/
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 .",
);
key = parsePlistXML(node.childNodes[i]);
} else {
invariant(
node.childNodes[i].nodeName !== "key",
'Unexpected key "' +
parsePlistXML(node.childNodes[i]) +
'" while parsing .',
);
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") {
// TODO: what should we do with text types? (CDATA sections)
} 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);
}
}