Created
June 3, 2011 21:36
-
-
Save clee/1007217 to your computer and use it in GitHub Desktop.
initial binary plist implementation in nodejs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
var fs = require('fs'); | |
var sys = require('sys'); | |
var bin = require('binary'); | |
function bytesize(n) { | |
// JavaScript can't handle 64-bit ints natively. | |
// TODO: hack it up anyway. | |
// (n & 0xFFFFFFFF00000000) ? 8 : ... | |
return ((n & 0xFFFF0000) ? 4 : ((n & 0xFF00) ? 2 : 1)); | |
} | |
function parseObject(data, offset, trailer) { | |
var marker = data[offset]; | |
var decoder = 'word' + (trailer.object_refsize * 8) + 'bu'; | |
// console.log("marker at offset " + offset + ": " + marker); | |
var object = null; | |
offset += 1; | |
switch (marker & 0xF0) { | |
case 0x00: // TYPE_BOOL | |
// console.log("decoding a bool"); | |
if ((marker ^ 0x08) == 0) { | |
object = true; | |
} else { | |
object = false; | |
} | |
break; | |
case 0x10: // TYPE_INT | |
var valsize = Math.pow(2, marker & 0x0F); | |
object = bin.parse(data.slice(offset, offset + valsize))['word' + 8 * valsize + 'bu']('intsize').vars.intsize; | |
// console.log("decoded an int (" + object + ") from actual offset (" + offset + ")"); | |
break; | |
case 0x20: // TYPE_REAL | |
var valsize = Math.pow(2, marker & 0x0F); | |
// console.log("raw data: " + sys.inspect(data.slice(offset, offset + valsize))); | |
object = bin.parse(data.slice(offset, offset + valsize))['word' + 8 * valsize + 'bu']('floatsize').vars.floatsize; | |
// console.log("decoded a float (" + object + ") from actual offset (" + offset + ")"); | |
break; | |
case 0x30: // TYPE_DATE | |
// console.log("raw data: " + data.slice(offset, offset + 8)); | |
object = bin.parse(data.slice(offset, offset + 8))['word64bu']('floatsize').vars.floatsize; | |
// console.log("decoded a date (" + object + ") from actual offset (" + offset + ")"); | |
break; | |
case 0x40: // TYPE_DATA | |
// console.log("decoding a data buffer"); | |
var data_length = ((marker & 0x0F) == 0x0F) ? parseObject(data, offset, trailer) : marker & 0x0F; | |
offset += ((data_length < 0x0F) ? 0 : (1 + bytesize(data_length))); | |
object = data.slice(offset, offset + data_length); | |
break; | |
case 0x50: // TYPE_STRING_ASCII | |
case 0x60: // TYPE_STRING_UNICODE | |
var string_length = 0; | |
var string_numchars = ((marker & 0x0F) == 0x0F) ? parseObject(data, offset, trailer) : marker & 0x0F; | |
if ((marker & 0xF0) == 0x60) { | |
string_length = 2 * string_numchars; | |
} else { | |
string_length = string_numchars; | |
} | |
var string_data = ''; | |
offset += ((string_numchars < 0x0F) ? 0 : (1 + bytesize(string_numchars))); | |
string_data = data.slice(offset, offset + string_length); | |
if ((marker & 0xF0) == 0x60) { | |
// console.log ("unicode string"); | |
var tmp = 0x0000; | |
for (var b = 0; b < string_length; b += 2) { | |
tmp = string_data[b] << 8 | string_data[b+1]; | |
string_data[b] = tmp & 0xFF00 >> 8; | |
string_data[b + 1] = tmp & 0x00FF << 8; | |
} | |
} | |
object = string_data.toString(((marker & 0xF0) == 0x50) ? 'ascii' : 'ucs2'); | |
// console.log("decoded a string (" + object + ") length (" + string_length + ")"); | |
break; | |
case 0x80: // TYPE_UID | |
// console.log("decoding a uid"); | |
object = (marker & 0x0F) + 1 | |
break; | |
case 0xA0: // TYPE_ARRAY | |
var array_size = ((marker & 0x0F) == 0x0F) ? | |
parseObject(data, offset, trailer) : | |
marker & 0x0F; | |
// console.log("decoding an array of size " + array_size); | |
object = []; | |
offset += ((array_size < 0x0F) ? 0 : (1 + bytesize(array_size))); | |
for (var i = 0; i < array_size; i++) { | |
var slice_start = offset + (i * trailer.object_refsize); | |
var slice_end = slice_start + trailer.object_refsize; | |
var obj_offset = bin.parse(data.slice(slice_start, slice_end))[decoder]('obj').vars.obj; | |
// console.log("pushing value from index " + obj_offset + " (from offset " + (slice_start) + " to end " + (slice_end) +")") | |
object.push(parseObject(data, trailer.offset_list[obj_offset], trailer)); | |
} | |
break; | |
case 0xD0: // TYPE_DICTIONARY | |
// console.log("decoding a dictionary"); | |
var dict_size = 0; | |
if ((marker & 0x0F) == 0x0F) { | |
dict_size = parseObject(data, offset, trailer); | |
} else { | |
dict_size = marker & 0x0F; | |
} | |
object = {}; | |
offset += ((dict_size < 0x0F) ? 0 : (1 + bytesize(dict_size))); | |
for (var i = 0; i < dict_size; i++) { | |
var key_slice_start = offset + (i * trailer.object_refsize); | |
var key_slice_end = key_slice_start + trailer.object_refsize; | |
var val_slice_start = offset + ((i + dict_size) * trailer.object_refsize); | |
var val_slice_end = val_slice_start + trailer.object_refsize; | |
// console.log ("key from (" + key_slice_start + " to " + key_slice_end + ") and val from (" + val_slice_start + " to " + val_slice_end + ")"); | |
var keyoffset = bin.parse(data.slice(key_slice_start, key_slice_end))[decoder]('key').vars.key; | |
var valoffset = bin.parse(data.slice(val_slice_start, val_slice_end))[decoder]('val').vars.val; | |
object[parseObject(data, trailer.offset_list[keyoffset], trailer)] = parseObject(data, trailer.offset_list[valoffset], trailer); | |
} | |
break; | |
default: | |
console.error("unknown object type: " + (marker & 0xF0)); | |
break; | |
} | |
return object; | |
} | |
function parseBinaryPlist(data, callback) { | |
if ('bplist00' != data.slice(0, 8).toString('ascii')) { | |
throw 'Not a binary plist!'; | |
} | |
// TODO: Better bplist validation checks | |
var trailer_data = data.slice(data.length - 32, data.length); | |
var trailer = bin.parse(trailer_data.slice(6, 32)) | |
.word8bu('offset_intsize') | |
.word8bu('object_refsize') | |
.word64bu('object_count') | |
.word64bu('top_level_object') | |
.word64bu('offset') | |
.vars; | |
if (1 || trailer.top_level_object != 0) { | |
console.log(JSON.stringify(trailer)); | |
} | |
// TODO: Check the trailer values to make sure they're sane | |
var offset_table_data = data.slice(trailer.offset, (trailer.object_count * trailer.offset_intsize) + trailer.offset); | |
var decode_method = 'word' + 8 * trailer.offset_intsize + 'bu'; | |
var offset_list = []; | |
for (var offset_index = 0; offset_index < trailer.object_count; offset_index++) { | |
var slice_start = offset_index * trailer.offset_intsize; | |
var slice_end = slice_start + trailer.offset_intsize; | |
var current_offset = offset_table_data.slice(slice_start, slice_end); | |
offset_list.push(bin.parse(current_offset)[decode_method]('o').vars.o); | |
} | |
trailer.offset_list = offset_list; | |
var object_tree = parseObject(data, offset_list[trailer.top_level_object], trailer); | |
sys.puts(sys.inspect(object_tree, true, 4)); | |
} | |
(function main() { | |
if (process.argv.length < 3) { | |
console.error("Need to specify input plist(s)"); | |
process.exit(-1); | |
} | |
process.argv.slice(2, process.argv.length).forEach(function(inputFileName) { | |
fs.readFile(inputFileName, function(err, data) { | |
if (err) { | |
console.log(err); | |
throw err; | |
} | |
console.log('-- Currently parsing: ' + inputFileName); | |
parseBinaryPlist(data); | |
}); | |
}); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment