diff options
Diffstat (limited to 'generator/node_modules/plist/lib')
-rw-r--r-- | generator/node_modules/plist/lib/build.js | 143 | ||||
-rw-r--r-- | generator/node_modules/plist/lib/parse.js | 227 |
2 files changed, 370 insertions, 0 deletions
diff --git a/generator/node_modules/plist/lib/build.js b/generator/node_modules/plist/lib/build.js new file mode 100644 index 0000000..c2a84b1 --- /dev/null +++ b/generator/node_modules/plist/lib/build.js @@ -0,0 +1,143 @@ +/** + * Module dependencies. + */ + +var base64 = require('base64-js'); +var xmlbuilder = require('xmlbuilder'); + +/** + * Module exports. + */ + +exports.build = build; + +/** + * Accepts a `Date` instance and returns an ISO date string. + * + * @param {Date} d - Date instance to serialize + * @returns {String} ISO date string representation of `d` + * @api private + */ + +function ISODateString(d){ + function pad(n){ + return n < 10 ? '0' + n : n; + } + return d.getUTCFullYear()+'-' + + pad(d.getUTCMonth()+1)+'-' + + pad(d.getUTCDate())+'T' + + pad(d.getUTCHours())+':' + + pad(d.getUTCMinutes())+':' + + pad(d.getUTCSeconds())+'Z'; +} + +/** + * Returns the internal "type" of `obj` via the + * `Object.prototype.toString()` trick. + * + * @param {Mixed} obj - any value + * @returns {String} the internal "type" name + * @api private + */ + +var toString = Object.prototype.toString; +function type (obj) { + var m = toString.call(obj).match(/\[object (.*)\]/); + return m ? m[1] : m; +} + +/** + * Generate an XML plist string from the input object `obj`. + * + * @param {Object} obj - the object to convert + * @param {Object} [opts] - optional options object + * @returns {String} converted plist XML string + * @api public + */ + +function build (obj, opts) { + var XMLHDR = { + version: '1.0', + encoding: 'UTF-8' + }; + + var XMLDTD = { + pubid: '-//Apple//DTD PLIST 1.0//EN', + sysid: 'http://www.apple.com/DTDs/PropertyList-1.0.dtd' + }; + + var doc = xmlbuilder.create('plist'); + + doc.dec(XMLHDR.version, XMLHDR.encoding, XMLHDR.standalone); + doc.dtd(XMLDTD.pubid, XMLDTD.sysid); + doc.att('version', '1.0'); + + walk_obj(obj, doc); + + if (!opts) opts = {}; + // default `pretty` to `true` + opts.pretty = opts.pretty !== false; + return doc.end(opts); +} + +/** + * depth first, recursive traversal of a javascript object. when complete, + * next_child contains a reference to the build XML object. + * + * @api private + */ + +function walk_obj(next, next_child) { + var tag_type, i, prop; + var name = type(next); + + if ('Undefined' == name) { + return; + } else if (Array.isArray(next)) { + next_child = next_child.ele('array'); + for (i = 0; i < next.length; i++) { + walk_obj(next[i], next_child); + } + + } else if (Buffer.isBuffer(next)) { + next_child.ele('data').raw(next.toString('base64')); + + } else if ('Object' == name) { + next_child = next_child.ele('dict'); + for (prop in next) { + if (next.hasOwnProperty(prop)) { + next_child.ele('key').txt(prop); + walk_obj(next[prop], next_child); + } + } + + } else if ('Number' == name) { + // detect if this is an integer or real + // TODO: add an ability to force one way or another via a "cast" + tag_type = (next % 1 === 0) ? 'integer' : 'real'; + next_child.ele(tag_type).txt(next.toString()); + + } else if ('BigInt' == name) { + next_child.ele('integer').txt(next); + + } else if ('Date' == name) { + next_child.ele('date').txt(ISODateString(new Date(next))); + + } else if ('Boolean' == name) { + next_child.ele(next ? 'true' : 'false'); + + } else if ('String' == name) { + next_child.ele('string').txt(next); + + } else if ('ArrayBuffer' == name) { + next_child.ele('data').raw(base64.fromByteArray(next)); + + } else if (next && next.buffer && 'ArrayBuffer' == type(next.buffer)) { + // a typed array + next_child.ele('data').raw(base64.fromByteArray(new Uint8Array(next.buffer), next_child)); + + } else if ('Null' === name) { + next_child.ele('null').txt(''); + + } +} diff --git a/generator/node_modules/plist/lib/parse.js b/generator/node_modules/plist/lib/parse.js new file mode 100644 index 0000000..b5e7496 --- /dev/null +++ b/generator/node_modules/plist/lib/parse.js @@ -0,0 +1,227 @@ +/** + * 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), <!-- xml comments -->, + * 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: + * <key /> + * 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); + invariant( + doc.documentElement.nodeName === 'plist', + 'malformed document. First element should be <plist>' + ); + var plist = parsePlistXML(doc.documentElement); + + // the root <plist> 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 <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') { + // 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); + } +} |