diff --git a/src/java/org/apache/poi/hssf/record/ObjRecord.java b/src/java/org/apache/poi/hssf/record/ObjRecord.java index c50f65b61..538b1ae9f 100644 --- a/src/java/org/apache/poi/hssf/record/ObjRecord.java +++ b/src/java/org/apache/poi/hssf/record/ObjRecord.java @@ -18,15 +18,13 @@ package org.apache.poi.hssf.record; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.util.ArrayList; import java.util.List; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.LittleEndianByteArrayOutputStream; import org.apache.poi.util.LittleEndianInputStream; -import org.apache.poi.util.LittleEndianOutput; -import org.apache.poi.util.LittleEndianOutputStream; /** * OBJRECORD (0x005D)

@@ -37,10 +35,17 @@ import org.apache.poi.util.LittleEndianOutputStream; */ public final class ObjRecord extends Record { public final static short sid = 0x005D; + + private static final int NORMAL_PAD_ALIGNMENT = 2; + private static int MAX_PAD_ALIGNMENT = 4; private List subrecords; /** used when POI has no idea what is going on */ private byte[] _uninterpretedData; + /** + * Excel seems to tolerate padding to quad or double byte length + */ + private boolean _isPaddedToQuadByteMultiple; //00000000 15 00 12 00 01 00 01 00 11 60 00 00 00 00 00 0D .........`...... //00000010 26 01 00 00 00 00 00 00 00 00 &......... @@ -71,6 +76,10 @@ public final class ObjRecord extends Record { _uninterpretedData = subRecordData; return; } + if (subRecordData.length % 2 != 0) { + String msg = "Unexpected length of subRecordData : " + HexDump.toHex(subRecordData); + throw new RecordFormatException(msg); + } // System.out.println(HexDump.toHex(subRecordData)); @@ -84,12 +93,17 @@ public final class ObjRecord extends Record { break; } } - if (bais.available() > 0) { - // earlier versions of the code had allowances for padding - // At present (Oct-2008), no unit test samples exhibit such padding - String msg = "Leftover " + bais.available() + int nRemainingBytes = bais.available(); + if (nRemainingBytes > 0) { + // At present (Oct-2008), most unit test samples have (subRecordData.length % 2 == 0) + _isPaddedToQuadByteMultiple = subRecordData.length % MAX_PAD_ALIGNMENT == 0; + if (nRemainingBytes >= (_isPaddedToQuadByteMultiple ? MAX_PAD_ALIGNMENT : NORMAL_PAD_ALIGNMENT)) { + String msg = "Leftover " + nRemainingBytes + " bytes in subrecord data " + HexDump.toHex(subRecordData); - throw new RecordFormatException(msg); + throw new RecordFormatException(msg); + } + } else { + _isPaddedToQuadByteMultiple = false; } } @@ -114,34 +128,41 @@ public final class ObjRecord extends Record { SubRecord record = (SubRecord) subrecords.get(i); size += record.getDataSize()+4; } + if (_isPaddedToQuadByteMultiple) { + while (size % MAX_PAD_ALIGNMENT != 0) { + size++; + } + } else { + while (size % NORMAL_PAD_ALIGNMENT != 0) { + size++; + } + } return size; } public int serialize(int offset, byte[] data) { int dataSize = getDataSize(); + int recSize = 4 + dataSize; + LittleEndianByteArrayOutputStream out = new LittleEndianByteArrayOutputStream(data, offset, recSize); - LittleEndian.putUShort(data, 0 + offset, sid); - LittleEndian.putUShort(data, 2 + offset, dataSize); + out.writeShort(sid); + out.writeShort(dataSize); - byte[] subRecordBytes; if (_uninterpretedData == null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(dataSize); - LittleEndianOutput leo = new LittleEndianOutputStream(baos); for (int i = 0; i < subrecords.size(); i++) { SubRecord record = (SubRecord) subrecords.get(i); - record.serialize(leo); + record.serialize(out); } + int expectedEndIx = offset+dataSize; // padding - while (baos.size() < dataSize) { - baos.write(0); + while (out.getWriteIndex() < expectedEndIx) { + out.writeByte(0); } - subRecordBytes = baos.toByteArray(); } else { - subRecordBytes = _uninterpretedData; + out.write(_uninterpretedData); } - System.arraycopy(subRecordBytes, 0, data, offset + 4, dataSize); - return 4 + dataSize; + return recSize; } public int getRecordSize() { diff --git a/src/testcases/org/apache/poi/hssf/record/TestObjRecord.java b/src/testcases/org/apache/poi/hssf/record/TestObjRecord.java index c143120de..4e928e878 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestObjRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestObjRecord.java @@ -20,6 +20,8 @@ package org.apache.poi.hssf.record; import java.util.Arrays; import java.util.List; +import org.apache.poi.util.HexRead; + import junit.framework.AssertionFailedError; import junit.framework.TestCase; @@ -38,17 +40,17 @@ public final class TestObjRecord extends TestCase { * [ftCmo] * [ftEnd] */ - private static final byte[] recdata = { - 0x15, 0x00, 0x12, 0x00, 0x06, 0x00, 0x01, 0x00, 0x11, 0x60, - (byte)0xF4, 0x02, 0x41, 0x01, 0x14, 0x10, 0x1F, 0x02, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - // TODO - this data seems to require two extra bytes padding. not sure where original file is. - // it's not bug 38607 attachment 17639 - }; + private static final byte[] recdata = HexRead.readFromString("" + + "15 00 12 00 06 00 01 00 11 60 " + + "F4 02 41 01 14 10 1F 02 00 00 " + +"00 00 00 00 00 00" + // TODO - this data seems to require two extra bytes padding. not sure where original file is. + // it's not bug 38607 attachment 17639 + ); - private static final byte[] recdataNeedingPadding = { - 21, 0, 18, 0, 0, 0, 1, 0, 17, 96, 0, 0, 0, 0, 56, 111, -52, 3, 0, 0, 0, 0, 6, 0, 2, 0, 0, 0, 0, 0, 0, 0 - }; + private static final byte[] recdataNeedingPadding = HexRead.readFromString("" + + "15 00 12 00 00 00 01 00 11 60 00 00 00 00 38 6F CC 03 00 00 00 00 06 00 02 00 00 00 00 00 00 00" + ); public void testLoad() { ObjRecord record = new ObjRecord(TestcaseRecordInputStream.create(ObjRecord.sid, recdata)); @@ -113,4 +115,23 @@ public final class TestObjRecord extends TestCase { assertEquals(GroupMarkerSubRecord.class, subrecords.get(1).getClass()); assertEquals(EndSubRecord.class, subrecords.get(2).getClass()); } + + /** + * Check that ObjRecord tolerates and preserves padding to a 4-byte boundary + * (normally padding is to a 2-byte boundary). + */ + public void test4BytePadding() { + // actual data from file saved by Excel 2007 + byte[] data = HexRead.readFromString("" + + "15 00 12 00 1E 00 01 00 11 60 B4 6D 3C 01 C4 06 " + + "49 06 00 00 00 00 00 00 00 00 00 00"); + // this data seems to have 2 extra bytes of padding more than usual + // the total may have been padded to the nearest quad-byte length + RecordInputStream in = TestcaseRecordInputStream.create(ObjRecord.sid, data); + // check read OK + ObjRecord record = new ObjRecord(in); + // check that it re-serializes to the same data + byte[] ser = record.serialize(); + TestcaseRecordInputStream.confirmRecordEncoding(ObjRecord.sid, data, ser); + } }