Skip to content

Instantly share code, notes, and snippets.

@clee
Created June 3, 2011 21:36
Show Gist options
  • Save clee/1007217 to your computer and use it in GitHub Desktop.
Save clee/1007217 to your computer and use it in GitHub Desktop.
initial binary plist implementation in nodejs
#!/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