
nl.pvanassen.bplist.parser.ElementParser Maven / Gradle / Ivy
The newest version!
package nl.pvanassen.bplist.parser;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import org.apache.commons.io.IOUtils;
import org.slf4j.*;
/**
* Parser for reading the bplist
*
* @author Paul van Assen
*/
public class ElementParser {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* Parse object table with a random access file. This method will not close
* the file for you.
*
* @param file File object
* @return List of objects parsed
* @throws IOException
* In case of an error
*/
public List> parseObjectTable(File file) throws IOException {
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(file, "r");
return parseObjectTable(raf);
} finally {
IOUtils.closeQuietly(raf);
}
}
/**
* Parse object table with a random access file. This method will not close
* the file for you.
*
* @param raf
* Random access file
* @return List of objects parsed
* @throws IOException
* In case of an error
*/
private List> parseObjectTable(RandomAccessFile raf) throws IOException {
// Parse the HEADER
// ----------------
// magic number ("bplist")
// file format version ("00")
int bpli = raf.readInt();
int st00 = raf.readInt();
if ((bpli != 0x62706c69) || (st00 != 0x73743030)) {
throw new IOException("parseHeader: File does not start with 'bplist00' magic.");
}
// Parse the TRAILER
// ----------------
// byte size of offset ints in offset table
// byte size of object refs in arrays and dicts
// number of offsets in offset table (also is number of objects)
// element # in offset table which is top level object
raf.seek(raf.length() - 32);
raf.readLong();
// count of object refs in arrays and dicts
int refCount = (int) raf.readLong();
raf.readLong();
// element # in offset table which is top level object
int topLevelOffset = (int) raf.readLong();
raf.seek(8);
// Read everything in memory hmmmm
byte[] buf = new byte[topLevelOffset - 8];
raf.readFully(buf);
ByteArrayInputStream stream = new ByteArrayInputStream(buf);
return parseObjectTable(new DataInputStream(stream), refCount);
}
/**
* Object Formats (marker byte followed by additional info in some cases)
*
* - null 0000 0000
* - bool 0000 1000 // false
* - bool 0000 1001 // true
* - fill 0000 1111 // fill byte
* - int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
* - real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
* - date 0011 0011 ... // 8 byte float follows, big-endian bytes
* - data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int count follows, followed by bytes
* - string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, if 1111 then int count, else bytes
* - string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else 1111 then int count, then big-endian 2-byte shorts
* - 0111 xxxx // unused
* - uid 1000 nnnn ... // nnnn+1 is # of bytes
* - 1001 xxxx // unused
* - array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int count follows
* - 1011 xxxx // unused
* - 1100 xxxx // unused
* - dict 1101 nnnn [int] keyref* objref* // nnnn is count, unless '1111', then int count follows
* - 1110 xxxx // unused
* - 1111 xxxx // unused
*
*/
private List> parseObjectTable(DataInputStream in, int refCount) throws IOException {
List> objectTable = new LinkedList>();
int marker;
while ((marker = in.read()) != -1) {
// System.err.println("parseObjectTable marker=" +
// Integer.toBinaryString(marker)+" 0x"+Integer.toHexString(marker)+" @0x"+Long.toHexString(getPosition()));
switch ((marker & 0xf0) >> 4) {
case 0: {
parseBoolean(marker & 0xf, objectTable);
break;
}
case 1: {
int count = 1 << (marker & 0xf);
parseInt(in, count, objectTable);
break;
}
case 2: {
int count = 1 << (marker & 0xf);
parseReal(in, count, objectTable);
break;
}
case 3: {
switch (marker & 0xf) {
case 3:
parseDate(in, objectTable);
break;
default:
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
}
break;
}
case 4: {
int count = marker & 0xf;
if (count == 15) {
count = readCount(in);
}
parseData(in, count, objectTable);
break;
}
case 5: {
int count = marker & 0xf;
if (count == 15) {
count = readCount(in);
}
parseAsciiString(in, count, objectTable);
break;
}
case 6: {
int count = marker & 0xf;
if (count == 15) {
count = readCount(in);
}
parseUnicodeString(in, count, objectTable);
break;
}
case 7: {
if (logger.isDebugEnabled()) {
logger.debug("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
}
return objectTable;
// throw new
// IOException("parseObjectTable: illegal marker "+Integer.toBinaryString(marker));
// break;
}
case 8: {
int count = (marker & 0xf) + 1;
if (logger.isDebugEnabled()) {
logger.debug("uid " + count);
}
parseUID(in, count, objectTable);
break;
}
case 9: {
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
// break;
}
case 10: {
int count = marker & 0xf;
if (count == 15) {
count = readCount(in);
}
if (refCount > 255) {
parseShortArray(in, count, objectTable);
} else {
parseByteArray(in, count, objectTable);
}
break;
}
case 11: {
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
// break;
}
case 12: {
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
// break;
}
case 13: {
int count = marker & 0xf;
if (count == 15) {
count = readCount(in);
}
if (refCount > 256) {
parseShortDict(in, count, objectTable);
} else {
parseByteDict(in, count, objectTable);
}
break;
}
case 14: {
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
// break;
}
case 15: {
throw new IOException("parseObjectTable: illegal marker " + Integer.toBinaryString(marker));
// break;
}
}
}
return objectTable;
}
/**
* Reads a count value from the object table. Count values are encoded using
* the following scheme:
* int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
*/
private int readCount(DataInputStream in) throws IOException {
int marker = in.read();
if (marker == -1) {
throw new IOException("variableLengthInt: Illegal EOF in marker");
}
if (((marker & 0xf0) >> 4) != 1) {
throw new IOException("variableLengthInt: Illegal marker " + Integer.toBinaryString(marker));
}
int count = 1 << (marker & 0xf);
int value = 0;
for (int i = 0; i < count; i++) {
int b = in.read();
if (b == -1) {
throw new IOException("variableLengthInt: Illegal EOF in value");
}
value = (value << 8) | b;
}
return value;
}
/**
* null 0000 0000 bool 0000 1000 // false bool 0000 1001 // true fill 0000
* 1111 // fill byte
*/
private void parseBoolean(int primitive, List> objectTable) throws IOException {
switch (primitive) {
case 0:
objectTable.add(null);
break;
case 8:
objectTable.add(BPListBoolean.FALSE);
break;
case 9:
objectTable.add(BPListBoolean.TRUE);
break;
case 15:
// fill byte: don't add to object table
break;
default:
throw new IOException("parsePrimitive: illegal primitive " + Integer.toBinaryString(primitive));
}
}
/**
* array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int
* count follows
*/
private void parseByteArray(DataInputStream in, int count, List> objectTable) throws IOException {
int[] objref = new int[count];
for (int i = 0; i < count; i++) {
objref[i] = in.readByte() & 0xff;
if (objref[i] == -1) {
throw new IOException("parseByteArray: illegal EOF in objref*");
}
}
objectTable.add(new BPLArray(objectTable, objref, BPListType.BYTE_ARRAY));
}
/**
* array 1010 nnnn [int] objref* // nnnn is count, unless '1111', then int
* count follows
*/
private void parseShortArray(DataInputStream in, int count, List> objectTable) throws IOException {
int[] objref = new int[count];
for (int i = 0; i < count; i++) {
objref[i] = in.readShort() & 0xffff;
if (objref[i] == -1) {
throw new IOException("parseShortArray: illegal EOF in objref*");
}
}
objectTable.add(new BPLArray(objectTable, objref, BPListType.SHORT_ARRAY));
}
/*
* data 0100 nnnn [int] ... // nnnn is number of bytes unless 1111 then int
* count follows, followed by bytes
*/
private void parseData(DataInputStream in, int count, List> objectTable) throws IOException {
byte[] data = new byte[count];
in.readFully(data);
objectTable.add(new BPListData(data));
}
/**
* byte dict 1101 nnnn keyref* objref* // nnnn is less than '1111'
*/
private void parseByteDict(DataInputStream in, int count, List> objectTable) throws IOException {
int[]keyref = new int[count];
int[]objref = new int[count];
for (int i = 0; i < count; i++) {
keyref[i] = in.readByte() & 0xff;
}
for (int i = 0; i < count; i++) {
objref[i] = in.readByte() & 0xff;
}
objectTable.add(new BPLDict(objectTable, keyref, objref, BPListType.BYTE_DICT));
}
/**
* short dict 1101 ffff int keyref* objref* // int is count
*/
private void parseShortDict(DataInputStream in, int count, List> objectTable) throws IOException {
int[]keyref = new int[count];
int[]objref = new int[count];
for (int i = 0; i < count; i++) {
keyref[i] = in.readShort() & 0xffff;
}
for (int i = 0; i < count; i++) {
objref[i] = in.readShort() & 0xffff;
}
objectTable.add(new BPLDict(objectTable, keyref, objref, BPListType.SHORT_DICT));
}
/**
* string 0101 nnnn [int] ... // ASCII string, nnnn is # of chars, else 1111
* then int count, then bytes
*/
private void parseAsciiString(DataInputStream in, int count, List> objectTable) throws IOException {
byte[] buf = new byte[count];
in.readFully(buf);
objectTable.add(new BPListString(buf));
}
private void parseUID(DataInputStream in, int count, List> objectTable) throws IOException {
if (count > 4) {
throw new IOException("parseUID: unsupported byte count: " + count);
}
byte[] uid = new byte[count];
in.readFully(uid);
objectTable.add(new BPLUid(new BigInteger(uid).intValue()));
}
/**
* int 0001 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
*/
private void parseInt(DataInputStream in, int count, List> objectTable) throws IOException {
if (count > 8) {
throw new IOException("parseInt: unsupported byte count: " + count);
}
long value = 0;
for (int i = 0; i < count; i++) {
int b = in.read();
if (b == -1) {
throw new IOException("parseInt: Illegal EOF in value");
}
value = (value << 8) | b;
}
objectTable.add(new BPListLong(value));
}
/**
* real 0010 nnnn ... // # of bytes is 2^nnnn, big-endian bytes
*/
private void parseReal(DataInputStream in, int count, List> objectTable) throws IOException {
switch (count) {
case 4:
objectTable.add(new BPListFloat(in.readFloat()));
break;
case 8:
objectTable.add(new BPListDouble(in.readDouble()));
break;
default:
throw new IOException("parseReal: unsupported byte count:" + count);
}
}
/**
* unknown 0011 0000 ... // 8 byte float follows, big-endian bytes
*/
/*
* private void parseUnknown(DataInputStream in) throws IOException {
* in.skipBytes(1); objectTable.add("unknown"); }
*/
/**
* date 0011 0011 ... // 8 byte float follows, big-endian bytes
*/
private void parseDate(DataInputStream in, List> objectTable) throws IOException {
objectTable.add(new BPListDate(in.readDouble()));
}
/**
* string 0110 nnnn [int] ... // Unicode string, nnnn is # of chars, else
* 1111 then int count, then big-endian 2-byte shorts
*/
private void parseUnicodeString(DataInputStream in, int count, List> objectTable) throws IOException {
char[] buf = new char[count];
for (int i = 0; i < count; i++) {
buf[i] = in.readChar();
}
objectTable.add(new BPListString(buf));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy