Fix for bug 45912 - ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@701569 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Josh Micich 2008-10-03 23:50:22 +00:00
parent 301a0f834c
commit dfc67205b9
6 changed files with 395 additions and 191 deletions

View File

@ -37,6 +37,7 @@
<!-- Don't forget to update status.xml too! --> <!-- Don't forget to update status.xml too! -->
<release version="3.2-alpha1" date="2008-??-??"> <release version="3.2-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
<action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action> <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
<action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action> <action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action> <action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action>

View File

@ -34,6 +34,7 @@
<!-- Don't forget to update changes.xml too! --> <!-- Don't forget to update changes.xml too! -->
<changes> <changes>
<release version="3.2-alpha1" date="2008-??-??"> <release version="3.2-alpha1" date="2008-??-??">
<action dev="POI-DEVELOPERS" type="fix">45912 - fixed ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord</action>
<action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action> <action dev="POI-DEVELOPERS" type="fix">45889 - fixed ArrayIndexOutOfBoundsException when constructing HSLF Table with a single row </action>
<action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action> <action dev="POI-DEVELOPERS" type="add">Initial support for creating hyperlinks in HSLF</action>
<action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action> <action dev="POI-DEVELOPERS" type="fix">45876 - fixed BoundSheetRecord to allow sheet names longer than 31 chars</action>

View File

@ -1,4 +1,3 @@
/* ==================================================================== /* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with contributor license agreements. See the NOTICE file distributed with
@ -16,122 +15,266 @@
limitations under the License. limitations under the License.
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.record; package org.apache.poi.hssf.record;
import java.io.ByteArrayInputStream;
import org.apache.poi.hssf.record.formula.Area3DPtg;
import org.apache.poi.util.*; 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)<br/>
* A sub-record within the OBJ record which stores a reference to an object * A sub-record within the OBJ record which stores a reference to an object
* stored in a separate entry within the OLE2 compound file. * stored in a separate entry within the OLE2 compound file.
* *
* @author Daniel Noll * @author Daniel Noll
*/ */
public class EmbeddedObjectRefSubRecord public final class EmbeddedObjectRefSubRecord extends SubRecord {
extends SubRecord public static final short sid = 0x0009;
{
public static final short sid = 0x9;
public short field_1_stream_id_offset; // Offset to stream ID from the point after this value. private static final byte[] EMPTY_BYTE_ARRAY = { };
public short[] field_2_unknown; // Unknown stuff at the front. TODO: Confirm that it's a short[]
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 // TODO: Consider making a utility class for these. I've discovered the same field ordering
// in FormatRecord and StringRecord, it may be elsewhere too. // in FormatRecord and StringRecord, it may be elsewhere too.
public short field_3_unicode_len; // Length of Unicode string. private boolean field_3_unicode_flag; // Flags whether the string is Unicode.
public boolean field_4_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)
public String field_5_ole_classname; // Classname of the embedded OLE document (e.g. Word.Document.8) /** Formulas often have a single non-zero trailing byte.
public int field_6_stream_id; // ID of the OLE stream containing the actual data. * 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;
private int field_5_ole_classname_padding; // developer laziness...
public byte[] remainingBytes;
public EmbeddedObjectRefSubRecord() // currently for testing only - needs review
{ EmbeddedObjectRefSubRecord() {
field_2_unknown = new short[0]; field_2_unknownFormulaData = new byte[] { 0x02, 0x6C, 0x6A, 0x16, 0x01, }; // just some sample data. These values vary a lot
remainingBytes = new byte[0]; field_6_unknown = EMPTY_BYTE_ARRAY;
field_1_stream_id_offset = 6; field_4_ole_classname = null;
field_5_ole_classname = "";
} }
public short getSid() public short getSid() {
{
return sid; return sid;
} }
public EmbeddedObjectRefSubRecord(RecordInputStream in) public EmbeddedObjectRefSubRecord(RecordInputStream in) {
{ // Much guess-work going on here due to lack of any documentation.
field_1_stream_id_offset = in.readShort(); // See similar source code in OOO:
field_2_unknown = in.readShortArray(); // http://lxr.go-oo.org/source/sc/sc/source/filter/excel/xiescher.cxx
field_3_unicode_len = in.readShort(); // 1223 void XclImpOleObj::ReadPictFmla( XclImpStream& rStrm, sal_uInt16 nRecSize )
field_4_unicode_flag = ( in.readByte() & 0x01 ) != 0;
if ( field_4_unicode_flag ) int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen'
{
field_5_ole_classname = in.readUnicodeLEString( field_3_unicode_len ); int dataLenAfterFormula = in.remaining() - streamIdOffset;
} int formulaSize = in.readUShort();
else field_1_unknown_int = in.readInt();
{ byte[] formulaRawBytes = readRawData(in, formulaSize);
field_5_ole_classname = in.readCompressedUnicode( field_3_unicode_len ); 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;
} }
// Padded with NUL bytes. The -2 is because field_1_stream_id_offset int stringByteCount;
// is relative to after the offset field, whereas in.getRecordOffset() if (in.remaining() >= dataLenAfterFormula + 3) {
// is relative to the start of this record (minus the header.) int tag = in.readByte();
field_5_ole_classname_padding = 0; if (tag != 0x03) {
while (in.getRecordOffset() - 2 < field_1_stream_id_offset) throw new RecordFormatException("Expected byte 0x03 here");
{ }
field_5_ole_classname_padding++; int nChars = in.readUShort();
in.readByte(); // discard 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;
if (nUnexpectedPadding > 0) {
System.err.println("Discarding " + nUnexpectedPadding + " unexpected padding bytes ");
readRawData(in, nUnexpectedPadding);
} }
// Fetch the stream ID // Fetch the stream ID
field_6_stream_id = in.readInt(); if (dataLenAfterFormula >= 4) {
field_5_stream_id = new Integer(in.readInt());
// Store what's left } else {
remainingBytes = in.readRemainder(); field_5_stream_id = null;
} }
public int serialize(int offset, byte[] data) field_6_unknown = in.readRemainder();
{
int pos = offset;
LittleEndian.putShort(data, pos, sid); pos += 2;
LittleEndian.putShort(data, pos, (short)(getRecordSize() - 4)); pos += 2;
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++;
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();
} }
// Padded with the same number of NUL bytes as were originally skipped. private static Ptg readRefPtg(byte[] formulaRawBytes) {
// XXX: This is only accurate until we make the classname mutable. byte[] data = new byte[formulaRawBytes.length + 4];
pos += field_5_ole_classname_padding; LittleEndian.putUShort(data, 0, -5555);
LittleEndian.putUShort(data, 2, formulaRawBytes.length);
LittleEndian.putInt(data, pos, field_6_stream_id); pos += 4; System.arraycopy(formulaRawBytes, 0, data, 4, formulaRawBytes.length);
RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
System.arraycopy(remainingBytes, 0, data, pos, remainingBytes.length); in.nextRecord();
byte ptgSid = in.readByte();
return getRecordSize(); 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;
} }
/** private static byte[] readRawData(RecordInputStream in, int size) {
* Size of record (exluding 4 byte header) if (size < 0) {
*/ throw new IllegalArgumentException("Negative size (" + size + ")");
public int getRecordSize() }
{ if (size == 0) {
// The stream id offset is relative to after the stream ID. return EMPTY_BYTE_ARRAY;
// 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; 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) {
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);
}
public int serialize(int base, byte[] data) {
int formulaSize = field_2_refPtg == null ? field_2_unknownFormulaData.length : field_2_refPtg.getSize();
int idOffset = getStreamIDOffset(formulaSize);
int dataSize = getDataSize(idOffset);
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();
} }
/** /**
@ -140,42 +283,44 @@ public class EmbeddedObjectRefSubRecord
* under the name "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> is * under the name "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> is
* this ID converted into hex (in big endian order, funnily enough.) * this ID converted into hex (in big endian order, funnily enough.)
* *
* @return the data stream ID. * @return the data stream ID. Possibly <code>null</code>
*/ */
public int getStreamId() public Integer getStreamId() {
{ return field_5_stream_id;
return field_6_stream_id;
} }
public String toString() public String getOLEClassName() {
{ return field_4_ole_classname;
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();
} }
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();
}
} }

View File

@ -32,7 +32,7 @@ import org.apache.poi.util.HexDump;
* *
* @author Daniel Noll * @author Daniel Noll
*/ */
public class HSSFObjectData public final class HSSFObjectData
{ {
/** /**
* Underlying object record ultimately containing a reference to the object. * 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 * Returns the OLE2 Class Name of the object
*/ */
public String getOLE2ClassName() { public String getOLE2ClassName() {
EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); return findObjectRecord().getOLEClassName();
return subRecord.field_5_ole_classname;
} }
/** /**
@ -74,7 +73,7 @@ public class HSSFObjectData
public DirectoryEntry getDirectory() throws IOException { 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); String streamName = "MBD" + HexDump.toHex(streamId);
Entry entry = poifs.getRoot().getEntry(streamName); Entry entry = poifs.getRoot().getEntry(streamName);
@ -91,8 +90,7 @@ public class HSSFObjectData
* Entry * Entry
*/ */
public byte[] getObjectData() { public byte[] getObjectData() {
EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); return findObjectRecord().getObjectData();
return subRecord.remainingBytes;
} }
/** /**
@ -103,8 +101,9 @@ public class HSSFObjectData
public boolean hasDirectoryEntry() { public boolean hasDirectoryEntry() {
EmbeddedObjectRefSubRecord subRecord = findObjectRecord(); EmbeddedObjectRefSubRecord subRecord = findObjectRecord();
// Field 6 tells you // 'stream id' field tells you
return (subRecord.field_6_stream_id != 0); Integer streamId = subRecord.getStreamId();
return streamId != null && streamId.intValue() != 0;
} }
/** /**

View File

@ -16,13 +16,13 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.hssf.record; 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.io.ByteArrayInputStream;
import java.util.Arrays; import java.util.Arrays;
import junit.framework.TestCase;
import org.apache.poi.util.HexRead;
/** /**
* Tests the serialization and deserialization of the TestEmbeddedObjectRefSubRecord * Tests the serialization and deserialization of the TestEmbeddedObjectRefSubRecord
* class works correctly. Test data taken directly from a real * class works correctly. Test data taken directly from a real
@ -30,13 +30,13 @@ import java.util.Arrays;
* *
* @author Yegor Kozlov * @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); byte[] src = hr(data1);
src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src); src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);
RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src)); RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src));
@ -51,17 +51,13 @@ public class TestEmbeddedObjectRefSubRecord extends TestCase {
EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);
assertTrue(Arrays.equals(src, ser)); assertTrue(Arrays.equals(src, ser));
assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset); assertEquals(record1.getOLEClassName(), record2.getOLEClassName());
assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown));
assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len); byte[] ser2 = record1.serialize();
assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag); assertTrue(Arrays.equals(ser, ser2));
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));
} }
public void testCreate() throws IOException { public void testCreate() {
EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(); EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord();
@ -70,13 +66,75 @@ public class TestEmbeddedObjectRefSubRecord extends TestCase {
in2.nextRecord(); in2.nextRecord();
EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2); EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);
assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset); assertEquals(record1.getOLEClassName(), record2.getOLEClassName());
assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown)); assertEquals(record1.getStreamId(), record2.getStreamId());
assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len);
assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag); byte[] ser2 = record1.serialize();
assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname); assertTrue(Arrays.equals(ser, ser2));
assertEquals(record1.field_6_stream_id, record2.field_6_stream_id); }
assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes));
/**
* 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");
public void testCameraTool_bug45912() {
byte[] data = data45912;
RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));
in.nextRecord();
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);
}
}
} }

View File

@ -969,7 +969,7 @@ public final class TestBugs extends TestCase {
public void test44840() { public void test44840() {
HSSFWorkbook wb = openSample("WithCheckBoxes.xls"); HSSFWorkbook wb = openSample("WithCheckBoxes.xls");
// Take a look at the embeded objects // Take a look at the embedded objects
List objects = wb.getAllEmbeddedObjects(); List objects = wb.getAllEmbeddedObjects();
assertEquals(1, objects.size()); assertEquals(1, objects.size());
@ -980,10 +980,10 @@ public final class TestBugs extends TestCase {
EmbeddedObjectRefSubRecord rec = obj.findObjectRecord(); EmbeddedObjectRefSubRecord rec = obj.findObjectRecord();
assertNotNull(rec); assertNotNull(rec);
assertEquals(32, rec.field_1_stream_id_offset); // assertEquals(32, rec.field_1_stream_id_offset);
assertEquals(0, rec.field_6_stream_id); // WRONG! assertEquals(0, rec.getStreamId().intValue()); // WRONG!
assertEquals("Forms.CheckBox.1", rec.field_5_ole_classname); assertEquals("Forms.CheckBox.1", rec.getOLEClassName());
assertEquals(12, rec.remainingBytes.length); assertEquals(12, rec.getObjectData().length);
// Doesn't have a directory // Doesn't have a directory
assertFalse(obj.hasDirectoryEntry()); assertFalse(obj.hasDirectoryEntry());
@ -995,7 +995,7 @@ public final class TestBugs extends TestCase {
obj.getDirectory(); obj.getDirectory();
fail(); fail();
} catch(FileNotFoundException e) { } catch(FileNotFoundException e) {
// expectd during successful test // expected during successful test
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }