]> source.dussan.org Git - poi.git/commitdiff
Fix for bug 45912 - ArrayIndexOutOfBoundsException in EmbeddedObjectRefSubRecord
authorJosh Micich <josh@apache.org>
Fri, 3 Oct 2008 23:50:22 +0000 (23:50 +0000)
committerJosh Micich <josh@apache.org>
Fri, 3 Oct 2008 23:50:22 +0000 (23:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@701569 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/changes.xml
src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java
src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java
src/testcases/org/apache/poi/hssf/record/TestEmbeddedObjectRefSubRecord.java
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java

index 6430582668566d152b1391bb659380ad9791a353..a30727d236c31ef7bce6fcd54df23933fdaedc7c 100644 (file)
@@ -37,6 +37,7 @@
 
                <!-- Don't forget to update status.xml too! -->
         <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="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>
index c998f61de9c679c2888413308637864ca8eb8b60..5083faa385ccf7dce90cb4663864fd41a18a048c 100644 (file)
@@ -34,6 +34,7 @@
        <!-- Don't forget to update changes.xml too! -->
     <changes>
         <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="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>
index fb6bce5d3a03310b7e0f9aa47a46313bea1640db..9e8d998b11ede515f299e62a3652a20c675d012e 100644 (file)
@@ -1,4 +1,3 @@
-
 /* ====================================================================
    Licensed to the Apache Software Foundation (ASF) under one or more
    contributor license agreements.  See the NOTICE file distributed with
    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)<br/>
  * 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 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 int field_5_ole_classname_padding; // developer laziness...
-    public byte[] remainingBytes;
-
-    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;
-    }
-
-    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;
-
-        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 );
-        }
-
-        // 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
-        }
-
-        // Fetch the stream ID
-        field_6_stream_id = in.readInt();
-        
-        // Store what's left
-        remainingBytes = in.readRemainder();
-    }
-
-    public int serialize(int offset, byte[] data)
-    {
-        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.
-        // 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;
-
-        System.arraycopy(remainingBytes, 0, data, pos, remainingBytes.length);
-
-        return getRecordSize();
-    }
-
-    /**
-     * 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;
-    }
-
-    /**
-     * 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 "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> 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;
-    }
-
-    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();
-    }
+public final class EmbeddedObjectRefSubRecord extends SubRecord {
+       public static final short sid = 0x0009;
+
+       private static final byte[] EMPTY_BYTE_ARRAY = { };
+
+       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;
+
+
+       // 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 short getSid() {
+               return sid;
+       }
+
+       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 )
+
+               int streamIdOffset = in.readShort(); // OOO calls this 'nFmlaLen'
+
+               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;
+               }
+
+               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;
+
+               if (nUnexpectedPadding > 0) {
+                       System.err.println("Discarding " + nUnexpectedPadding + " unexpected padding bytes ");
+                       readRawData(in, nUnexpectedPadding);
+               }
+
+               // Fetch the stream ID
+               if (dataLenAfterFormula >= 4) {
+                       field_5_stream_id = new Integer(in.readInt());
+               } else {
+                       field_5_stream_id = null;
+               }
+
+               field_6_unknown = in.readRemainder();
+       }
+
+       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;
+       }
+
+       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) {
+
+               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();
+       }
+
+       /**
+        * 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 "MBD<var>xxxxxxxx</var>" where <var>xxxxxxxx</var> is
+        * this ID converted into hex (in big endian order, funnily enough.)
+        *
+        * @return the data stream ID. Possibly <code>null</code>
+        */
+       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();
+       }
 }
index 697c33b9e2b9d5731bcff562b6123992f49b36de..1db58833031d877bcf30f2049200458749d63eed 100644 (file)
@@ -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;
             }
         }
         
index feb13757fc4ac2b3c14e3406d81f26f870242286..e09f9e34ab1ccc3e4501b7a216623c7cfeff2916 100644 (file)
 ==================================================================== */\r
 package org.apache.poi.hssf.record;\r
 \r
-import junit.framework.TestCase;\r
-import org.apache.poi.util.HexRead;\r
-\r
-import java.io.IOException;\r
 import java.io.ByteArrayInputStream;\r
 import java.util.Arrays;\r
 \r
+import junit.framework.TestCase;\r
+\r
+import org.apache.poi.util.HexRead;\r
+\r
 /**\r
  * Tests the serialization and deserialization of the TestEmbeddedObjectRefSubRecord\r
  * class works correctly.  Test data taken directly from a real\r
@@ -30,53 +30,111 @@ import java.util.Arrays;
  *\r
  * @author Yegor Kozlov\r
  */\r
-public class TestEmbeddedObjectRefSubRecord extends TestCase {\r
-\r
-    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, ]";\r
-\r
-    public void testStore() throws IOException {\r
-\r
-        byte[] src = HexRead.readFromString(data1);\r
-        src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);\r
-\r
-        RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src));\r
-        in.nextRecord();\r
-\r
-        EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in);\r
-\r
-        byte[] ser = record1.serialize();\r
-\r
-        RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
-        in2.nextRecord();\r
-        EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
-\r
-        assertTrue(Arrays.equals(src, ser));\r
-        assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset);\r
-        assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown));\r
-        assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len);\r
-        assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag);\r
-        assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname);\r
-        assertEquals(record1.field_6_stream_id, record2.field_6_stream_id);\r
-        assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes));\r
-    }\r
-\r
-    public void testCreate() throws IOException {\r
-\r
-\r
-        EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord();\r
-\r
-        byte[] ser = record1.serialize();\r
-        RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
-        in2.nextRecord();\r
-        EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
-\r
-        assertEquals(record1.field_1_stream_id_offset, record2.field_1_stream_id_offset);\r
-        assertTrue(Arrays.equals(record1.field_2_unknown, record2.field_2_unknown));\r
-        assertEquals(record1.field_3_unicode_len, record2.field_3_unicode_len);\r
-        assertEquals(record1.field_4_unicode_flag, record2.field_4_unicode_flag);\r
-        assertEquals(record1.field_5_ole_classname, record2.field_5_ole_classname);\r
-        assertEquals(record1.field_6_stream_id, record2.field_6_stream_id);\r
-        assertTrue(Arrays.equals(record1.remainingBytes, record2.remainingBytes));\r
+public final class TestEmbeddedObjectRefSubRecord extends TestCase {\r
 \r
-    }\r
+       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, ]";\r
+\r
+       public void testStore() {\r
+\r
+               byte[] src = hr(data1);\r
+               src = TestcaseRecordInputStream.mergeDataAndSid(EmbeddedObjectRefSubRecord.sid, (short)src.length, src);\r
+\r
+               RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(src));\r
+               in.nextRecord();\r
+\r
+               EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord(in);\r
+\r
+               byte[] ser = record1.serialize();\r
+\r
+               RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
+               in2.nextRecord();\r
+               EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
+\r
+               assertTrue(Arrays.equals(src, ser));\r
+               assertEquals(record1.getOLEClassName(), record2.getOLEClassName());\r
+\r
+               byte[] ser2 = record1.serialize();\r
+               assertTrue(Arrays.equals(ser, ser2));\r
+       }\r
+\r
+       public void testCreate() {\r
+\r
+               EmbeddedObjectRefSubRecord record1 = new EmbeddedObjectRefSubRecord();\r
+\r
+               byte[] ser = record1.serialize();\r
+               RecordInputStream in2 = new RecordInputStream(new ByteArrayInputStream(ser));\r
+               in2.nextRecord();\r
+               EmbeddedObjectRefSubRecord record2 = new EmbeddedObjectRefSubRecord(in2);\r
+\r
+               assertEquals(record1.getOLEClassName(), record2.getOLEClassName());\r
+               assertEquals(record1.getStreamId(), record2.getStreamId());\r
+\r
+               byte[] ser2 = record1.serialize();\r
+               assertTrue(Arrays.equals(ser, ser2));\r
+       }\r
+\r
+\r
+       /**\r
+        * taken from ftPictFmla sub-record in attachment 22645 (offset 0x40AB).\r
+        */\r
+       private static final byte[] data45912 = hr(\r
+               "09 00 14 00 " +\r
+               "12 00 0B 00 F8 02 88 04 3B 00 " +\r
+               "00 00 00 01 00 00 00 01 " +\r
+               "00 00");\r
+\r
+       public void testCameraTool_bug45912() {\r
+               byte[] data = data45912;\r
+               RecordInputStream in = new RecordInputStream(new ByteArrayInputStream(data));\r
+               in.nextRecord();\r
+\r
+               EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in);\r
+               byte[] ser2 = rec.serialize();\r
+               assertTrue(Arrays.equals(data, ser2));\r
+\r
+\r
+       }\r
+\r
+       private static byte[] hr(String string) {\r
+               return HexRead.readFromString(string);\r
+       }\r
+\r
+       /**\r
+        * tests various examples of OLE controls\r
+        */\r
+       public void testVarious() {\r
+               String[] rawData = {\r
+                       "12 00 0B 00 70 95 0B 05 3B 01 00 36 00 40 00 18 00 19 00 18",\r
+                       "12 00 0B 00 B0 4D 3E 03 3B 00 00 00 00 01 00 00 80 01 C0 00",\r
+                       "0C 00 05 00 60 AF 3B 03 24 FD FF FE C0 FE",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+                       "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",\r
+               };\r
+\r
+               for (int i = 0; i < rawData.length; i++) {\r
+                       confirmRead(hr(rawData[i]), i);\r
+               }\r
+       }\r
+\r
+       private static void confirmRead(byte[] data, int i) {\r
+               RecordInputStream in = new TestcaseRecordInputStream(EmbeddedObjectRefSubRecord.sid, (short)data.length, data);\r
+\r
+               EmbeddedObjectRefSubRecord rec = new EmbeddedObjectRefSubRecord(in);\r
+               byte[] ser2 = rec.serialize();\r
+               byte[] d2 = (byte[]) data.clone(); // remove sid+len for compare\r
+               System.arraycopy(ser2, 4, d2, 0, d2.length);\r
+               if (!Arrays.equals(data, d2)) {\r
+                       fail("re-read NQR for case " + i);\r
+               }\r
+       }\r
 }\r
index ea807f0542f92f4469f34c39efd0cb7c389d7bd5..fdaf1d04d4a33ea9247c7c39905a940a6dd22368 100644 (file)
@@ -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);
         }