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

bplistParser.js 11KB

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