diff --git a/src/main/java/nl/pvanassen/bplist/converter/ConvertToXml.java b/src/main/java/nl/pvanassen/bplist/converter/ConvertToXml.java index 247714b..151e455 100644 --- a/src/main/java/nl/pvanassen/bplist/converter/ConvertToXml.java +++ b/src/main/java/nl/pvanassen/bplist/converter/ConvertToXml.java @@ -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(), 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. diff --git a/src/main/java/nl/pvanassen/bplist/parser/ElementParser.java b/src/main/java/nl/pvanassen/bplist/parser/ElementParser.java index c79b268..d34c7ed 100644 --- a/src/main/java/nl/pvanassen/bplist/parser/ElementParser.java +++ b/src/main/java/nl/pvanassen/bplist/parser/ElementParser.java @@ -4,6 +4,7 @@ import java.math.BigInteger; import java.util.*; + import org.apache.commons.io.IOUtils; import org.slf4j.*; @@ -79,6 +80,69 @@ private List> 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> 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) *
    diff --git a/src/test/java/nl/pvanassen/bplist/BinaryPListParserTest.java b/src/test/java/nl/pvanassen/bplist/BinaryPListParserTest.java index af7db49..736b1c2 100644 --- a/src/test/java/nl/pvanassen/bplist/BinaryPListParserTest.java +++ b/src/test/java/nl/pvanassen/bplist/BinaryPListParserTest.java @@ -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; @@ -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> 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"); @@ -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"); + } }