From dfc67205b9a6c7abc4bb73003f19328d91f6c027 Mon Sep 17 00:00:00 2001 From: Josh Micich Date: Fri, 3 Oct 2008 23:50:22 +0000 Subject: [PATCH] Fix for bug 45912 - ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@701569 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../record/EmbeddedObjectRefSubRecord.java | 413 ++++++++++++------ .../poi/hssf/usermodel/HSSFObjectData.java | 23 +- .../TestEmbeddedObjectRefSubRecord.java | 136 ++++-- .../apache/poi/hssf/usermodel/TestBugs.java | 12 +- 6 files changed, 395 insertions(+), 191 deletions(-) diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 6430582668..a30727d236 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row Initial support for creating hyperlinks in HSLF 45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index c998f61de9..5083faa385 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord 45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row Initial support for creating hyperlinks in HSLF 45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars diff --git a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java index fb6bce5d3a..9e8d998b11 100644 --- a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java +++ b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -16,166 +15,312 @@ limitations under the License. ==================================================================== */ - package org.apache.poi.hssf.record; +import java.io.ByteArrayInputStream; - -import org.apache.poi.util.*; +import org.apache.poi.hssf.record.formula.Area3DPtg; +import org.apache.poi.hssf.record.formula.AreaPtg; +import org.apache.poi.hssf.record.formula.Ptg; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.record.formula.RefPtg; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; /** + * ftPictFmla (0x0009)
* A sub-record within the OBJ record which stores a reference to an object * stored in a separate entry within the OLE2 compound file. * * @author Daniel Noll */ -public class EmbeddedObjectRefSubRecord - extends SubRecord -{ - public static final short sid = 0x9; +public final class EmbeddedObjectRefSubRecord extends SubRecord { + public static final short sid = 0x0009; - public short field_1_stream_id_offset; // Offset to stream ID from the point after this value. - public short[] field_2_unknown; // Unknown stuff at the front. TODO: Confirm that it's a short[] - // TODO: Consider making a utility class for these. I've discovered the same field ordering - // in FormatRecord and StringRecord, it may be elsewhere too. - public short field_3_unicode_len; // Length of Unicode string. - public boolean field_4_unicode_flag; // Flags whether the string is Unicode. - public String field_5_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8) - public int field_6_stream_id; // ID of the OLE stream containing the actual data. + private static final byte[] EMPTY_BYTE_ARRAY = { }; - private int field_5_ole_classname_padding; // developer laziness... - public byte[] remainingBytes; + private int field_1_unknown_int; + /** either an area or a cell ref */ + private Ptg field_2_refPtg; + private byte[] field_2_unknownFormulaData; + // TODO: Consider making a utility class for these. I've discovered the same field ordering + // in FormatRecord and StringRecord, it may be elsewhere too. + private boolean field_3_unicode_flag; // Flags whether the string is Unicode. + private String field_4_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8) + /** Formulas often have a single non-zero trailing byte. + * This is in a similar position to he pre-streamId padding + * It is unknown if the value is important (it seems to mirror a value a few bytes earlier) + * */ + private Byte field_4_unknownByte; + private Integer field_5_stream_id; // ID of the OLE stream containing the actual data. + private byte[] field_6_unknown; - public EmbeddedObjectRefSubRecord() - { - field_2_unknown = new short[0]; - remainingBytes = new byte[0]; - field_1_stream_id_offset = 6; - field_5_ole_classname = ""; - } - public short getSid() - { - return sid; - } + // currently for testing only - needs review + EmbeddedObjectRefSubRecord() { + field_2_unknownFormulaData = new byte[] { 0x02, 0x6C, 0x6A, 0x16, 0x01, }; // just some sample data. These values vary a lot + field_6_unknown = EMPTY_BYTE_ARRAY; + field_4_ole_classname = null; + } - public EmbeddedObjectRefSubRecord(RecordInputStream in) - { - field_1_stream_id_offset = in.readShort(); - field_2_unknown = in.readShortArray(); - field_3_unicode_len = in.readShort(); - field_4_unicode_flag = ( in.readByte() & 0x01 ) != 0; + public short getSid() { + return sid; + } - if ( field_4_unicode_flag ) - { - field_5_ole_classname = in.readUnicodeLEString( field_3_unicode_len ); - } - else - { - field_5_ole_classname = in.readCompressedUnicode( field_3_unicode_len ); - } + public EmbeddedObjectRefSubRecord(RecordInputStream in) { + // Much guess-work going on here due to lack of any documentation. + // See similar source code in OOO: + // http://lxr.go-oo.org/source/sc/sc/source/filter/excel/xiescher.cxx + // 1223 void XclImpOleObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nRecSize ) - // Padded with NUL bytes. The -2 is because field_1_stream_id_offset - // is relative to after the offset field, whereas in.getRecordOffset() - // is relative to the start of this record (minus the header.) - field_5_ole_classname_padding = 0; - while (in.getRecordOffset() - 2 < field_1_stream_id_offset) - { - field_5_ole_classname_padding++; - in.readByte(); // discard - } + int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen' - // Fetch the stream ID - field_6_stream_id = in.readInt(); - - // Store what's left - remainingBytes = in.readRemainder(); - } + int dataLenAfterFormula = in.remaining() - streamIdOffset; + int formulaSize = in.readUShort(); + field_1_unknown_int = in.readInt(); + byte[] formulaRawBytes = readRawData(in, formulaSize); + field_2_refPtg = readRefPtg(formulaRawBytes); + if (field_2_refPtg == null) { + // common case + // field_2_n16 seems to be 5 here + // The formula almost looks like tTbl but the row/column values seem like garbage. + field_2_unknownFormulaData = formulaRawBytes; + } else { + field_2_unknownFormulaData = null; + } - public int serialize(int offset, byte[] data) - { - int pos = offset; + int stringByteCount; + if (in.remaining() >= dataLenAfterFormula + 3) { + int tag = in.readByte(); + if (tag != 0x03) { + throw new RecordFormatException("Expected byte 0x03 here"); + } + int nChars = in.readUShort(); + if (nChars > 0) { + // OOO: the 4th way Xcl stores a unicode string: not even a Grbit byte present if length 0 + field_3_unicode_flag = ( in.readByte() & 0x01 ) != 0; + if (field_3_unicode_flag) { + field_4_ole_classname = in.readUnicodeLEString(nChars); + stringByteCount = nChars * 2; + } else { + field_4_ole_classname = in.readCompressedUnicode(nChars); + stringByteCount = nChars; + } + } else { + field_4_ole_classname = ""; + stringByteCount = 0; + } + } else { + field_4_ole_classname = null; + stringByteCount = 0; + } + // Pad to next 2-byte boundary + if (((stringByteCount + formulaSize) % 2) != 0) { + int b = in.readByte(); + if (field_2_refPtg != null && field_4_ole_classname == null) { + field_4_unknownByte = new Byte((byte)b); + } + } + int nUnexpectedPadding = in.remaining() - dataLenAfterFormula; - LittleEndian.putShort(data, pos, sid); pos += 2; - LittleEndian.putShort(data, pos, (short)(getRecordSize() - 4)); pos += 2; + if (nUnexpectedPadding > 0) { + System.err.println("Discarding " + nUnexpectedPadding + " unexpected padding bytes "); + readRawData(in, nUnexpectedPadding); + } - LittleEndian.putShort(data, pos, field_1_stream_id_offset); pos += 2; - LittleEndian.putShortArray(data, pos, field_2_unknown); pos += field_2_unknown.length * 2 + 2; - LittleEndian.putShort(data, pos, field_3_unicode_len); pos += 2; - data[pos] = field_4_unicode_flag ? (byte) 0x01 : (byte) 0x00; pos++; + // Fetch the stream ID + if (dataLenAfterFormula >= 4) { + field_5_stream_id = new Integer(in.readInt()); + } else { + field_5_stream_id = null; + } - if ( field_4_unicode_flag ) - { - StringUtil.putUnicodeLE( field_5_ole_classname, data, pos ); pos += field_5_ole_classname.length() * 2; - } - else - { - StringUtil.putCompressedUnicode( field_5_ole_classname, data, pos ); pos += field_5_ole_classname.length(); - } + field_6_unknown = in.readRemainder(); + } - // Padded with the same number of NUL bytes as were originally skipped. - // XXX: This is only accurate until we make the classname mutable. - pos += field_5_ole_classname_padding; - - LittleEndian.putInt(data, pos, field_6_stream_id); pos += 4; + private static Ptg readRefPtg(byte[] formulaRawBytes) { + byte[] data = new byte[formulaRawBytes.length + 4]; + LittleEndian.putUShort(data, 0, -5555); + LittleEndian.putUShort(data, 2, formulaRawBytes.length); + System.arraycopy(formulaRawBytes, 0, data, 4, formulaRawBytes.length); + RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data)); + in.nextRecord(); + byte ptgSid = in.readByte(); + switch(ptgSid) { + case AreaPtg.sid: return new AreaPtg(in); + case Area3DPtg.sid: return new Area3DPtg(in); + case RefPtg.sid: return new RefPtg(in); + case Ref3DPtg.sid: return new Ref3DPtg(in); + } + return null; + } - System.arraycopy(remainingBytes, 0, data, pos, remainingBytes.length); + private static byte[] readRawData(RecordInputStream in, int size) { + if (size < 0) { + throw new IllegalArgumentException("Negative size (" + size + ")"); + } + if (size == 0) { + return EMPTY_BYTE_ARRAY; + } + byte[] result = new byte[size]; + for(int i=0; i< size; i++) { + result[i] = in.readByte(); + } + return result; + } + + private int getStreamIDOffset(int formulaSize) { + int result = 2 + 4; // formulaSize + f2unknown_int + result += formulaSize; + + int stringLen; + if (field_4_ole_classname == null) { + // don't write 0x03, stringLen, flag, text + stringLen = 0; + } else { + result += 1 + 2 + 1; // 0x03, stringLen, flag + stringLen = field_4_ole_classname.length(); + if (field_3_unicode_flag) { + result += stringLen * 2; + } else { + result += stringLen; + } + } + // pad to next 2 byte boundary + if ((result % 2) != 0) { + result ++; + } + return result; + } + + private int getDataSize(int idOffset) { - return getRecordSize(); - } + int result = 2 + idOffset; // 2 for idOffset short field itself + if (field_5_stream_id != null) { + result += 4; + } + return result + field_6_unknown.length; + } + private int getDataSize() { + int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize(); + int idOffset = getStreamIDOffset(formulaSize); + return getDataSize(idOffset); + } - /** - * Size of record (exluding 4 byte header) - */ - public int getRecordSize() - { - // The stream id offset is relative to after the stream ID. - // Add 2 bytes for the stream id offset and 4 bytes for the stream id itself and 4 byts for the record header. - return remainingBytes.length + field_1_stream_id_offset + 2 + 4 + 4; - } + public int serialize(int base, byte[] data) { - /** - * Gets the stream ID containing the actual data. The data itself - * can be found under a top-level directory entry in the OLE2 filesystem - * under the name "MBDxxxxxxxx" where xxxxxxxx is - * this ID converted into hex (in big endian order, funnily enough.) - * - * @return the data stream ID. - */ - public int getStreamId() - { - return field_6_stream_id; - } + int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize(); + int idOffset = getStreamIDOffset(formulaSize); + int dataSize = getDataSize(idOffset); + - public String toString() - { - StringBuffer buffer = new StringBuffer(); - buffer.append("[ftPictFmla]\n"); - buffer.append(" .streamIdOffset = ") - .append("0x").append(HexDump.toHex( field_1_stream_id_offset )) - .append(" (").append( field_1_stream_id_offset ).append(" )") - .append(System.getProperty("line.separator")); - buffer.append(" .unknown = ") - .append("0x").append(HexDump.toHex( field_2_unknown )) - .append(" (").append( field_2_unknown.length ).append(" )") - .append(System.getProperty("line.separator")); - buffer.append(" .unicodeLen = ") - .append("0x").append(HexDump.toHex( field_3_unicode_len )) - .append(" (").append( field_3_unicode_len ).append(" )") - .append(System.getProperty("line.separator")); - buffer.append(" .unicodeFlag = ") - .append("0x").append( field_4_unicode_flag ? 0x01 : 0x00 ) - .append(" (").append( field_4_unicode_flag ).append(" )") - .append(System.getProperty("line.separator")); - buffer.append(" .oleClassname = ") - .append(field_5_ole_classname) - .append(System.getProperty("line.separator")); - buffer.append(" .streamId = ") - .append("0x").append(HexDump.toHex( field_6_stream_id )) - .append(" (").append( field_6_stream_id ).append(" )") - .append(System.getProperty("line.separator")); - buffer.append("[/ftPictFmla]"); - return buffer.toString(); - } + LittleEndian.putUShort(data, base + 0, sid); + LittleEndian.putUShort(data, base + 2, dataSize); + LittleEndian.putUShort(data, base + 4, idOffset); + LittleEndian.putUShort(data, base + 6, formulaSize); + LittleEndian.putInt(data, base + 8, field_1_unknown_int); + + int pos = base+12; + + if (field_2_refPtg == null) { + System.arraycopy(field_2_unknownFormulaData, 0, data, pos, field_2_unknownFormulaData.length); + } else { + field_2_refPtg.writeBytes(data, pos); + } + pos += formulaSize; + + int stringLen; + if (field_4_ole_classname == null) { + // don't write 0x03, stringLen, flag, text + stringLen = 0; + } else { + LittleEndian.putByte(data, pos, 0x03); + pos += 1; + stringLen = field_4_ole_classname.length(); + LittleEndian.putUShort(data, pos, stringLen); + pos += 2; + LittleEndian.putByte(data, pos, field_3_unicode_flag ? 0x01 : 0x00); + pos += 1; + + if (field_3_unicode_flag) { + StringUtil.putUnicodeLE(field_4_ole_classname, data, pos); + pos += stringLen * 2; + } else { + StringUtil.putCompressedUnicode(field_4_ole_classname, data, pos); + pos += stringLen; + } + } + + // pad to next 2-byte boundary (requires 0 or 1 bytes) + switch(idOffset - (pos - 6 - base)) { // 6 for 3 shorts: sid, dataSize, idOffset + case 1: + LittleEndian.putByte(data, pos, field_4_unknownByte == null ? 0x00 : field_4_unknownByte.intValue()); + pos ++; + case 0: + break; + default: + throw new IllegalStateException("Bad padding calculation (" + idOffset + ", " + (pos-base) + ")"); + } + + if (field_5_stream_id != null) { + LittleEndian.putInt(data, pos, field_5_stream_id.intValue()); + pos += 4; + } + System.arraycopy(field_6_unknown, 0, data, pos, field_6_unknown.length); + + return 4 + dataSize; + } + + public int getRecordSize() { + return 4 + getDataSize(); + } + + /** + * Gets the stream ID containing the actual data. The data itself + * can be found under a top-level directory entry in the OLE2 filesystem + * under the name "MBDxxxxxxxx" where xxxxxxxx is + * this ID converted into hex (in big endian order, funnily enough.) + * + * @return the data stream ID. Possibly null + */ + public Integer getStreamId() { + return field_5_stream_id; + } + + public String getOLEClassName() { + return field_4_ole_classname; + } + + public byte[] getObjectData() { + return field_6_unknown; + } + + + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("[ftPictFmla]\n"); + sb.append(" .f2unknown = ").append(HexDump.intToHex(field_1_unknown_int)).append("\n"); + if (field_2_refPtg == null) { + sb.append(" .f3unknown = ").append(HexDump.toHex(field_2_unknownFormulaData)).append("\n"); + } else { + sb.append(" .formula = ").append(field_2_refPtg.toString()).append("\n"); + } + if (field_4_ole_classname != null) { + sb.append(" .unicodeFlag = ").append(field_3_unicode_flag).append("\n"); + sb.append(" .oleClassname = ").append(field_4_ole_classname).append("\n"); + } + if (field_4_unknownByte != null) { + sb.append(" .f4unknown = ").append(HexDump.byteToHex(field_4_unknownByte.intValue())).append("\n"); + } + if (field_5_stream_id != null) { + sb.append(" .streamId = ").append(HexDump.intToHex(field_5_stream_id.intValue())).append("\n"); + } + if (field_6_unknown.length > 0) { + sb.append(" .f7unknown = ").append(HexDump.toHex(field_6_unknown)).append("\n"); + } + sb.append("[/ftPictFmla]"); + return sb.toString(); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java index 697c33b9e2..1db5883303 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java @@ -32,7 +32,7 @@ import org.apache.poi.util.HexDump; * * @author Daniel Noll */ -public class HSSFObjectData +public final class HSSFObjectData { /** * Underlying object record ultimately containing a reference to the object. @@ -60,8 +60,7 @@ public class HSSFObjectData * Returns the OLE2 Class Name of the object */ public String getOLE2ClassName() { - EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); - return subRecord.field_5_ole_classname; + return findObjectRecord().getOLEClassName(); } /** @@ -72,9 +71,9 @@ public class HSSFObjectData * @throws IOException if there was an error reading the data. */ public DirectoryEntry getDirectory() throws IOException { - EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); + EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); - int streamId = ((EmbeddedObjectRefSubRecord) subRecord).getStreamId(); + int streamId = subRecord.getStreamId().intValue(); String streamName = "MBD" + HexDump.toHex(streamId); Entry entry = poifs.getRoot().getEntry(streamName); @@ -91,8 +90,7 @@ public class HSSFObjectData * Entry */ public byte[] getObjectData() { - EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); - return subRecord.remainingBytes; + return findObjectRecord().getObjectData(); } /** @@ -101,10 +99,11 @@ public class HSSFObjectData * (Not all do, those that don't have a data portion) */ public boolean hasDirectoryEntry() { - EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); - - // Field 6 tells you - return (subRecord.field_6_stream_id != 0); + EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); + + // 'stream id' field tells you + Integer streamId = subRecord.getStreamId(); + return streamId != null && streamId.intValue() != 0; } /** @@ -117,7 +116,7 @@ public class HSSFObjectData while (subRecordIter.hasNext()) { Object subRecord = subRecordIter.next(); if (subRecord instanceof EmbeddedObjectRefSubRecord) { - return (EmbeddedObjectRefSubRecord)subRecord; + return (EmbeddedObjectRefSubRecord)subRecord; } } diff --git a/src/testcases/org/apache/poi/hssf/record/TestEmbeddedObjectRefSubRecord.java b/src/testcases/org/apache/poi/hssf/record/TestEmbeddedObjectRefSubRecord.java index feb13757fc..e09f9e34ab 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestEmbeddedObjectRefSubRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestEmbeddedObjectRefSubRecord.java @@ -16,13 +16,13 @@ ==================================================================== */ package org.apache.poi.hssf.record; -import junit.framework.TestCase; -import org.apache.poi.util.HexRead; - -import java.io.IOException; import java.io.ByteArrayInputStream; import java.util.Arrays; +import junit.framework.TestCase; + +import org.apache.poi.util.HexRead; + /** * Tests the serialization and deserialization of the TestEmbeddedObjectRefSubRecord * class works correctly. Test data taken directly from a real @@ -30,53 +30,111 @@ import java.util.Arrays; * * @author Yegor Kozlov */ -public class TestEmbeddedObjectRefSubRecord extends TestCase { +public final class TestEmbeddedObjectRefSubRecord extends TestCase { - String data1 = "[20, 00, 05, 00, FC, 10, 76, 01, 02, 24, 14, DF, 00, 03, 10, 00, 00, 46, 6F, 72, 6D, 73, 2E, 43, 68, 65, 63, 6B, 42, 6F, 78, 2E, 31, 00, 00, 00, 00, 00, 70, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, ]"; + String data1 = "[20, 00, 05, 00, FC, 10, 76, 01, 02, 24, 14, DF, 00, 03, 10, 00, 00, 46, 6F, 72, 6D, 73, 2E, 43, 68, 65, 63, 6B, 42, 6F, 78, 2E, 31, 00, 00, 00, 00, 00, 70, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, ]"; - public void testStore() throws IOException { + public void testStore() { - byte[] src = HexRead.readFromString(data1); - src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src); + byte[] src = hr(data1); + src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src); - RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src)); - in.nextRecord(); + RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src)); + in.nextRecord(); - EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in); + EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in); - byte[] ser = record1.serialize(); + byte[] ser = record1.serialize(); - RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser)); - in2.nextRecord(); - EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); + RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser)); + in2.nextRecord(); + EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); - assertTrue(Arrays.equals(src, ser)); - assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset); - assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown)); - assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len); - assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag); - assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname); - assertEquals(record1.field_6_stream_id, record2.field_6_stream_id); - assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes)); - } + assertTrue(Arrays.equals(src, ser)); + assertEquals(record1.getOLEClassName(), record2.getOLEClassName()); - public void testCreate() throws IOException { + byte[] ser2 = record1.serialize(); + assertTrue(Arrays.equals(ser, ser2)); + } + + public void testCreate() { + + EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(); + + byte[] ser = record1.serialize(); + RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser)); + in2.nextRecord(); + EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); + + assertEquals(record1.getOLEClassName(), record2.getOLEClassName()); + assertEquals(record1.getStreamId(), record2.getStreamId()); + + byte[] ser2 = record1.serialize(); + assertTrue(Arrays.equals(ser, ser2)); + } - EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(); + /** + * taken from ftPictFmla sub-record in attachment 22645 (offset 0x40AB). + */ + private static final byte[] data45912 = hr( + "09 00 14 00 " + + "12 00 0B 00 F8 02 88 04 3B 00 " + + "00 00 00 01 00 00 00 01 " + + "00 00"); - byte[] ser = record1.serialize(); - RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser)); - in2.nextRecord(); - EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); + public void testCameraTool_bug45912() { + byte[] data = data45912; + RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data)); + in.nextRecord(); - assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset); - assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown)); - assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len); - assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag); - assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname); - assertEquals(record1.field_6_stream_id, record2.field_6_stream_id); - assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes)); + EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in); + byte[] ser2 = rec.serialize(); + assertTrue(Arrays.equals(data, ser2)); - } + + } + + private static byte[] hr(String string) { + return HexRead.readFromString(string); + } + + /** + * tests various examples of OLE controls + */ + public void testVarious() { + String[] rawData = { + "12 00 0B 00 70 95 0B 05 3B 01 00 36 00 40 00 18 00 19 00 18", + "12 00 0B 00 B0 4D 3E 03 3B 00 00 00 00 01 00 00 80 01 C0 00", + "0C 00 05 00 60 AF 3B 03 24 FD FF FE C0 FE", + "24 00 05 00 40 42 3E 03 02 80 CD B4 04 03 15 00 00 46 6F 72 6D 73 2E 43 6F 6D 6D 61 6E 64 42 75 74 74 6F 6E 2E 31 00 00 00 00 54 00 00 00 00 00 00 00 00 00 00 00", + "22 00 05 00 10 4E 3E 03 02 00 4C CC 04 03 12 00 00 46 6F 72 6D 73 2E 53 70 69 6E 42 75 74 74 6F 6E 2E 31 00 54 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00", + "20 00 05 00 E0 41 3E 03 02 00 FC 0B 05 03 10 00 00 46 6F 72 6D 73 2E 43 6F 6D 62 6F 42 6F 78 2E 31 00 74 00 00 00 4C 00 00 00 00 00 00 00 00 00 00 00", + "24 00 05 00 00 4C AF 03 02 80 E1 93 05 03 14 00 00 46 6F 72 6D 73 2E 4F 70 74 69 6F 6E 42 75 74 74 6F 6E 2E 31 00 C0 00 00 00 70 00 00 00 00 00 00 00 00 00 00 00", + "20 00 05 00 E0 A4 28 04 02 80 EA 93 05 03 10 00 00 46 6F 72 6D 73 2E 43 68 65 63 6B 42 6F 78 2E 31 00 30 01 00 00 6C 00 00 00 00 00 00 00 00 00 00 00", + "1C 00 05 00 30 40 3E 03 02 00 CC B4 04 03 0D 00 00 46 6F 72 6D 73 2E 4C 61 62 65 6C 2E 31 9C 01 00 00 54 00 00 00 00 00 00 00 00 00 00 00", + "1E 00 05 00 B0 A4 28 04 02 00 D0 0A 05 03 0F 00 00 46 6F 72 6D 73 2E 4C 69 73 74 42 6F 78 2E 31 F0 01 00 00 48 00 00 00 00 00 00 00 00 00 00 00", + "24 00 05 00 C0 AF 3B 03 02 80 D1 0A 05 03 14 00 00 46 6F 72 6D 73 2E 54 6F 67 67 6C 65 42 75 74 74 6F 6E 2E 31 00 38 02 00 00 6C 00 00 00 00 00 00 00 00 00 00 00", + "1E 00 05 00 90 AF 3B 03 02 80 D4 0A 05 03 0F 00 00 46 6F 72 6D 73 2E 54 65 78 74 42 6F 78 2E 31 A4 02 00 00 48 00 00 00 00 00 00 00 00 00 00 00", + "24 00 05 00 60 40 3E 03 02 00 D6 0A 05 03 14 00 00 46 6F 72 6D 73 2E 54 6F 67 67 6C 65 42 75 74 74 6F 6E 2E 31 00 EC 02 00 00 6C 00 00 00 00 00 00 00 00 00 00 00", + "20 00 05 00 20 4D 3E 03 02 00 D9 0A 05 03 11 00 00 46 6F 72 6D 73 2E 53 63 72 6F 6C 6C 42 61 72 2E 31 58 03 00 00 20 00 00 00 00 00 00 00 00 00 00 00", + "20 00 05 00 00 AF 28 04 02 80 31 AC 04 03 10 00 00 53 68 65 6C 6C 2E 45 78 70 6C 6F 72 65 72 2E 32 00 78 03 00 00 AC 00 00 00 00 00 00 00 00 00 00 00", + }; + + for (int i = 0; i < rawData.length; i++) { + confirmRead(hr(rawData[i]), i); + } + } + + private static void confirmRead(byte[] data, int i) { + RecordInputStream in = new TestcaseRecordInputStream(EmbeddedObjectRefSubRecord.sid, (short)data.length, data); + + EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in); + byte[] ser2 = rec.serialize(); + byte[] d2 = (byte[]) data.clone(); // remove sid+len for compare + System.arraycopy(ser2, 4, d2, 0, d2.length); + if (!Arrays.equals(data, d2)) { + fail("re-read NQR for case " + i); + } + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index ea807f0542..fdaf1d04d4 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -969,7 +969,7 @@ public final class TestBugs extends TestCase { public void test44840() { HSSFWorkbook wb = openSample("WithCheckBoxes.xls"); - // Take a look at the embeded objects + // Take a look at the embedded objects List objects = wb.getAllEmbeddedObjects(); assertEquals(1, objects.size()); @@ -980,10 +980,10 @@ public final class TestBugs extends TestCase { EmbeddedObjectRefSubRecord rec = obj.findObjectRecord(); assertNotNull(rec); - assertEquals(32, rec.field_1_stream_id_offset); - assertEquals(0, rec.field_6_stream_id); // WRONG! - assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname); - assertEquals(12, rec.remainingBytes.length); +// assertEquals(32, rec.field_1_stream_id_offset); + assertEquals(0, rec.getStreamId().intValue()); // WRONG! + assertEquals("Forms.CheckBox.1", rec.getOLEClassName()); + assertEquals(12, rec.getObjectData().length); // Doesn't have a directory assertFalse(obj.hasDirectoryEntry()); @@ -995,7 +995,7 @@ public final class TestBugs extends TestCase { obj.getDirectory(); fail(); } catch(FileNotFoundException e) { - // expectd during successful test + // expected during successful test } catch (IOException e) { throw new RuntimeException(e); }