Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/main/java/nl/pvanassen/bplist/converter/ConvertToXml.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,23 @@ public XMLElement convertToXml(File file) throws IOException {
return root;
}

/**
* Parses a binary PList from input stream and turns it into a XMLElement. The XMLElement
* is equivalent with a XML PList file parsed using NanoXML.
*
* @param is bplist input stream to parse
* @return Returns the parsed XMLElement.
* @throws IOException If the file is not found
*/
public XMLElement convertToXml(InputStream is) throws IOException {
// Convert the object table to XML and return it
XMLElement root = new XMLElement(new HashMap<String, char[]>(), false, false);
root.setName("plist");
root.setAttribute("version", "1.0");
convertObjectTableToXML(root, parser.parseObjectTable(is).get(0));
return root;
}

/**
* Parses a binary PList file and turns it into a XMLElement. The XMLElement
* is equivalent with a XML PList file parsed using NanoXML.
Expand Down
64 changes: 64 additions & 0 deletions src/main/java/nl/pvanassen/bplist/parser/ElementParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.math.BigInteger;
import java.util.*;


import org.apache.commons.io.IOUtils;
import org.slf4j.*;

Expand Down Expand Up @@ -79,6 +80,69 @@ private List<BPListElement<?>> parseObjectTable(RandomAccessFile raf) throws IOE
return parseObjectTable(new DataInputStream(stream), refCount);
}

/**
* Parse object table with an input stream. This method will not close
* the input stream for you.
*
* @param is
* Input stream
* @return List of objects parsed
* @throws IOException
* In case of an error
*/
public List<BPListElement<?>> parseObjectTable(InputStream is) throws IOException{
// mimic using a raf but with a InputStream so that we can read from memory if wanted
// supports only input streams that allows reset()
//byte[] intBuffer = new byte[4];
//byte[] longBuffer = new byte[8];
//was going to extend RandomAccessFile but realise I can't initialize it without a real file
//assume that everything happens here were in memory or some other InputStream that has no File
byte[] readBuffer = new byte[1024];
int length = 0;
int readSize;
is.reset();
while((readSize = is.read(readBuffer, 0, 1024)) != -1){
length += readSize;
}
// read bpli and st00 from header
is.reset();
readSize = is.read(readBuffer, 0, 8);
if(readSize != 8){
throw new IOException("parseHeader: File too small to be a bplist.");
}
int bpli = ((readBuffer[0] & 0xFF) << 24) | ((readBuffer[1] & 0xFF) << 16) | ((readBuffer[2] & 0xFF) << 8) | (readBuffer[3] & 0xFF);
int st00 = ((readBuffer[4] & 0xFF) << 24) | ((readBuffer[5] & 0xFF) << 16) | ((readBuffer[6] & 0xFF) << 8) | (readBuffer[7] & 0xFF);
if ((bpli != 0x62706c69) || (st00 != 0x73743030)) {
throw new IOException("parseHeader: File does not start with 'bplist00' magic.");
}
// read refCount and topLevelOffset from trailer
is.reset();
is.skip(length - 32);
readSize = is.read(readBuffer, 0, 16);
if(readSize != 16){
throw new IOException("parseHeader: File too small to be a bplist.");
}
int refCount = ((readBuffer[12] & 0xFF) << 24) | ((readBuffer[13] & 0xFF) << 16) | ((readBuffer[14] & 0xFF) << 8) | (readBuffer[15] & 0xFF);
readSize = is.read(readBuffer, 0, 16);
if(readSize != 16){
throw new IOException("parseHeader: File too small to be a bplist.");
}
int topLevelOffset = ((readBuffer[12] & 0xFF) << 24) | ((readBuffer[13] & 0xFF) << 16) | ((readBuffer[14] & 0xFF) << 8) | (readBuffer[15] & 0xFF);

is.reset();
is.skip(8);
// read all to memory
byte[] buf = new byte[topLevelOffset - 8];
readSize = is.read(buf, 0, topLevelOffset - 8);
if(readSize != (topLevelOffset - 8)){
throw new IOException("parseHeader: File too small to be a bplist.");
}
ByteArrayInputStream stream = new ByteArrayInputStream(buf);

return parseObjectTable(new DataInputStream(stream), refCount);

}

/**
* Object Formats (marker byte followed by additional info in some cases)
* <ul>
Expand Down
48 changes: 47 additions & 1 deletion src/test/java/nl/pvanassen/bplist/BinaryPListParserTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package nl.pvanassen.bplist;

import java.io.IOException;
import java.io.FileInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.util.List;

import nl.pvanassen.bplist.converter.ConvertToXml;
Expand All @@ -20,12 +23,28 @@ private void test(String baseName) throws IOException {
assertNotNull(xmlElement);
assertEquals(FileHelper.getContent(baseName + ".result"), xmlElement.getChildren().get(0).toString());
}

private void testInputStreamMode(String baseName) throws IOException {
//List<BPListElement<?>> elements = elementParser.parseObjectTable(new FileInputStream(FileHelper.getFile(baseName + ".bplist")));
byte[] copyBuffer = new byte[1024];
int copySize = 0;
FileInputStream testFile = new FileInputStream(FileHelper.getFile(baseName + ".bplist"));
ByteArrayOutputStream memory = new ByteArrayOutputStream();
while((copySize = testFile.read(copyBuffer, 0, 1024)) != -1){
memory.write(copyBuffer, 0, copySize);
}
testFile.close();
ByteArrayInputStream memoryStream = new ByteArrayInputStream(memory.toByteArray());
XMLElement xmlElement = convetToXml.convertToXml(memoryStream);
assertNotNull(xmlElement);
assertEquals(FileHelper.getContent(baseName + ".result"), xmlElement.getChildren().get(0).toString());
}

@Test
public void testAirplay() throws IOException {
test("airplay");
}

@Test
public void testITunesSmall() throws IOException {
test("iTunes-small");
Expand All @@ -48,5 +67,32 @@ public void testUID() throws IOException {
public void testUTF16() throws IOException {
test("utf16");
}

@Test
public void testInputStreamModeAirPlay() throws IOException {
testInputStreamMode("airplay");
}

@Test
public void testInputStreamModeITunesSmall() throws IOException {
testInputStreamMode("iTunes-small");
}

@Test
public void testInputStreamModeSample1() throws IOException {
testInputStreamMode("sample1");
}

@Test
public void testInputStreamModeSample2() throws IOException {
testInputStreamMode("sample2");
}
@Test
public void testInputStreamModeUID() throws IOException {
testInputStreamMode("uid");
}
@Test
public void testInputStreamModeUTF16() throws IOException {
testInputStreamMode("utf16");
}
}