Repositorio del curso CCOM4030 el semestre B91 del proyecto Artesanías con el Instituto de Cultura

bplistParser.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357
  1. 'use strict';
  2. // adapted from http://code.google.com/p/plist/source/browse/trunk/src/com/dd/plist/BinaryPropertyListParser.java
  3. var fs = require('fs');
  4. var bigInt = require("big-integer");
  5. var debug = false;
  6. exports.maxObjectSize = 100 * 1000 * 1000; // 100Meg
  7. exports.maxObjectCount = 32768;
  8. // EPOCH = new SimpleDateFormat("yyyy MM dd zzz").parse("2001 01 01 GMT").getTime();
  9. // ...but that's annoying in a static initializer because it can throw exceptions, ick.
  10. // So we just hardcode the correct value.
  11. var EPOCH = 978307200000;
  12. // UID object definition
  13. var UID = exports.UID = function(id) {
  14. this.UID = id;
  15. }
  16. var parseFile = exports.parseFile = function (fileNameOrBuffer, callback) {
  17. function tryParseBuffer(buffer) {
  18. var err = null;
  19. var result;
  20. try {
  21. result = parseBuffer(buffer);
  22. } catch (ex) {
  23. err = ex;
  24. }
  25. callback(err, result);
  26. }
  27. if (Buffer.isBuffer(fileNameOrBuffer)) {
  28. return tryParseBuffer(fileNameOrBuffer);
  29. } else {
  30. fs.readFile(fileNameOrBuffer, function (err, data) {
  31. if (err) { return callback(err); }
  32. tryParseBuffer(data);
  33. });
  34. }
  35. };
  36. var parseBuffer = exports.parseBuffer = function (buffer) {
  37. var result = {};
  38. // check header
  39. var header = buffer.slice(0, 'bplist'.length).toString('utf8');
  40. if (header !== 'bplist') {
  41. throw new Error("Invalid binary plist. Expected 'bplist' at offset 0.");
  42. }
  43. // Handle trailer, last 32 bytes of the file
  44. var trailer = buffer.slice(buffer.length - 32, buffer.length);
  45. // 6 null bytes (index 0 to 5)
  46. var offsetSize = trailer.readUInt8(6);
  47. if (debug) {
  48. console.log("offsetSize: " + offsetSize);
  49. }
  50. var objectRefSize = trailer.readUInt8(7);
  51. if (debug) {
  52. console.log("objectRefSize: " + objectRefSize);
  53. }
  54. var numObjects = readUInt64BE(trailer, 8);
  55. if (debug) {
  56. console.log("numObjects: " + numObjects);
  57. }
  58. var topObject = readUInt64BE(trailer, 16);
  59. if (debug) {
  60. console.log("topObject: " + topObject);
  61. }
  62. var offsetTableOffset = readUInt64BE(trailer, 24);
  63. if (debug) {
  64. console.log("offsetTableOffset: " + offsetTableOffset);
  65. }
  66. if (numObjects > exports.maxObjectCount) {
  67. throw new Error("maxObjectCount exceeded");
  68. }
  69. // Handle offset table
  70. var offsetTable = [];
  71. for (var i = 0; i < numObjects; i++) {
  72. var offsetBytes = buffer.slice(offsetTableOffset + i * offsetSize, offsetTableOffset + (i + 1) * offsetSize);
  73. offsetTable[i] = readUInt(offsetBytes, 0);
  74. if (debug) {
  75. console.log("Offset for Object #" + i + " is " + offsetTable[i] + " [" + offsetTable[i].toString(16) + "]");
  76. }
  77. }
  78. // Parses an object inside the currently parsed binary property list.
  79. // For the format specification check
  80. // <a href="http://www.opensource.apple.com/source/CF/CF-635/CFBinaryPList.c">
  81. // Apple's binary property list parser implementation</a>.
  82. function parseObject(tableOffset) {
  83. var offset = offsetTable[tableOffset];
  84. var type = buffer[offset];
  85. var objType = (type & 0xF0) >> 4; //First 4 bits
  86. var objInfo = (type & 0x0F); //Second 4 bits
  87. switch (objType) {
  88. case 0x0:
  89. return parseSimple();
  90. case 0x1:
  91. return parseInteger();
  92. case 0x8:
  93. return parseUID();
  94. case 0x2:
  95. return parseReal();
  96. case 0x3:
  97. return parseDate();
  98. case 0x4:
  99. return parseData();
  100. case 0x5: // ASCII
  101. return parsePlistString();
  102. case 0x6: // UTF-16
  103. return parsePlistString(true);
  104. case 0xA:
  105. return parseArray();
  106. case 0xD:
  107. return parseDictionary();
  108. default:
  109. throw new Error("Unhandled type 0x" + objType.toString(16));
  110. }
  111. function parseSimple() {
  112. //Simple
  113. switch (objInfo) {
  114. case 0x0: // null
  115. return null;
  116. case 0x8: // false
  117. return false;
  118. case 0x9: // true
  119. return true;
  120. case 0xF: // filler byte
  121. return null;
  122. default:
  123. throw new Error("Unhandled simple type 0x" + objType.toString(16));
  124. }
  125. }
  126. function bufferToHexString(buffer) {
  127. var str = '';
  128. var i;
  129. for (i = 0; i < buffer.length; i++) {
  130. if (buffer[i] != 0x00) {
  131. break;
  132. }
  133. }
  134. for (; i < buffer.length; i++) {
  135. var part = '00' + buffer[i].toString(16);
  136. str += part.substr(part.length - 2);
  137. }
  138. return str;
  139. }
  140. function parseInteger() {
  141. var length = Math.pow(2, objInfo);
  142. if (length > 4) {
  143. var data = buffer.slice(offset + 1, offset + 1 + length);
  144. var str = bufferToHexString(data);
  145. return bigInt(str, 16);
  146. } if (length < exports.maxObjectSize) {
  147. return readUInt(buffer.slice(offset + 1, offset + 1 + length));
  148. } else {
  149. throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
  150. }
  151. }
  152. function parseUID() {
  153. var length = objInfo + 1;
  154. if (length < exports.maxObjectSize) {
  155. return new UID(readUInt(buffer.slice(offset + 1, offset + 1 + length)));
  156. } else {
  157. throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
  158. }
  159. }
  160. function parseReal() {
  161. var length = Math.pow(2, objInfo);
  162. if (length < exports.maxObjectSize) {
  163. var realBuffer = buffer.slice(offset + 1, offset + 1 + length);
  164. if (length === 4) {
  165. return realBuffer.readFloatBE(0);
  166. }
  167. else if (length === 8) {
  168. return realBuffer.readDoubleBE(0);
  169. }
  170. } else {
  171. throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
  172. }
  173. }
  174. function parseDate() {
  175. if (objInfo != 0x3) {
  176. console.error("Unknown date type :" + objInfo + ". Parsing anyway...");
  177. }
  178. var dateBuffer = buffer.slice(offset + 1, offset + 9);
  179. return new Date(EPOCH + (1000 * dateBuffer.readDoubleBE(0)));
  180. }
  181. function parseData() {
  182. var dataoffset = 1;
  183. var length = objInfo;
  184. if (objInfo == 0xF) {
  185. var int_type = buffer[offset + 1];
  186. var intType = (int_type & 0xF0) / 0x10;
  187. if (intType != 0x1) {
  188. console.error("0x4: UNEXPECTED LENGTH-INT TYPE! " + intType);
  189. }
  190. var intInfo = int_type & 0x0F;
  191. var intLength = Math.pow(2, intInfo);
  192. dataoffset = 2 + intLength;
  193. if (intLength < 3) {
  194. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  195. } else {
  196. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  197. }
  198. }
  199. if (length < exports.maxObjectSize) {
  200. return buffer.slice(offset + dataoffset, offset + dataoffset + length);
  201. } else {
  202. throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
  203. }
  204. }
  205. function parsePlistString (isUtf16) {
  206. isUtf16 = isUtf16 || 0;
  207. var enc = "utf8";
  208. var length = objInfo;
  209. var stroffset = 1;
  210. if (objInfo == 0xF) {
  211. var int_type = buffer[offset + 1];
  212. var intType = (int_type & 0xF0) / 0x10;
  213. if (intType != 0x1) {
  214. console.err("UNEXPECTED LENGTH-INT TYPE! " + intType);
  215. }
  216. var intInfo = int_type & 0x0F;
  217. var intLength = Math.pow(2, intInfo);
  218. var stroffset = 2 + intLength;
  219. if (intLength < 3) {
  220. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  221. } else {
  222. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  223. }
  224. }
  225. // length is String length -> to get byte length multiply by 2, as 1 character takes 2 bytes in UTF-16
  226. length *= (isUtf16 + 1);
  227. if (length < exports.maxObjectSize) {
  228. var plistString = new Buffer(buffer.slice(offset + stroffset, offset + stroffset + length));
  229. if (isUtf16) {
  230. plistString = swapBytes(plistString);
  231. enc = "ucs2";
  232. }
  233. return plistString.toString(enc);
  234. } else {
  235. throw new Error("To little heap space available! Wanted to read " + length + " bytes, but only " + exports.maxObjectSize + " are available.");
  236. }
  237. }
  238. function parseArray() {
  239. var length = objInfo;
  240. var arrayoffset = 1;
  241. if (objInfo == 0xF) {
  242. var int_type = buffer[offset + 1];
  243. var intType = (int_type & 0xF0) / 0x10;
  244. if (intType != 0x1) {
  245. console.error("0xa: UNEXPECTED LENGTH-INT TYPE! " + intType);
  246. }
  247. var intInfo = int_type & 0x0F;
  248. var intLength = Math.pow(2, intInfo);
  249. arrayoffset = 2 + intLength;
  250. if (intLength < 3) {
  251. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  252. } else {
  253. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  254. }
  255. }
  256. if (length * objectRefSize > exports.maxObjectSize) {
  257. throw new Error("To little heap space available!");
  258. }
  259. var array = [];
  260. for (var i = 0; i < length; i++) {
  261. var objRef = readUInt(buffer.slice(offset + arrayoffset + i * objectRefSize, offset + arrayoffset + (i + 1) * objectRefSize));
  262. array[i] = parseObject(objRef);
  263. }
  264. return array;
  265. }
  266. function parseDictionary() {
  267. var length = objInfo;
  268. var dictoffset = 1;
  269. if (objInfo == 0xF) {
  270. var int_type = buffer[offset + 1];
  271. var intType = (int_type & 0xF0) / 0x10;
  272. if (intType != 0x1) {
  273. console.error("0xD: UNEXPECTED LENGTH-INT TYPE! " + intType);
  274. }
  275. var intInfo = int_type & 0x0F;
  276. var intLength = Math.pow(2, intInfo);
  277. dictoffset = 2 + intLength;
  278. if (intLength < 3) {
  279. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  280. } else {
  281. length = readUInt(buffer.slice(offset + 2, offset + 2 + intLength));
  282. }
  283. }
  284. if (length * 2 * objectRefSize > exports.maxObjectSize) {
  285. throw new Error("To little heap space available!");
  286. }
  287. if (debug) {
  288. console.log("Parsing dictionary #" + tableOffset);
  289. }
  290. var dict = {};
  291. for (var i = 0; i < length; i++) {
  292. var keyRef = readUInt(buffer.slice(offset + dictoffset + i * objectRefSize, offset + dictoffset + (i + 1) * objectRefSize));
  293. var valRef = readUInt(buffer.slice(offset + dictoffset + (length * objectRefSize) + i * objectRefSize, offset + dictoffset + (length * objectRefSize) + (i + 1) * objectRefSize));
  294. var key = parseObject(keyRef);
  295. var val = parseObject(valRef);
  296. if (debug) {
  297. console.log(" DICT #" + tableOffset + ": Mapped " + key + " to " + val);
  298. }
  299. dict[key] = val;
  300. }
  301. return dict;
  302. }
  303. }
  304. return [ parseObject(topObject) ];
  305. };
  306. function readUInt(buffer, start) {
  307. start = start || 0;
  308. var l = 0;
  309. for (var i = start; i < buffer.length; i++) {
  310. l <<= 8;
  311. l |= buffer[i] & 0xFF;
  312. }
  313. return l;
  314. }
  315. // we're just going to toss the high order bits because javascript doesn't have 64-bit ints
  316. function readUInt64BE(buffer, start) {
  317. var data = buffer.slice(start, start + 8);
  318. return data.readUInt32BE(4, 8);
  319. }
  320. function swapBytes(buffer) {
  321. var len = buffer.length;
  322. for (var i = 0; i < len; i += 2) {
  323. var a = buffer[i];
  324. buffer[i] = buffer[i+1];
  325. buffer[i+1] = a;
  326. }
  327. return buffer;
  328. }