]> source.dussan.org Git - poi.git/commitdiff
HPSF: Enable new number types in VariantSupport
authorAndreas Beeker <kiwiwings@apache.org>
Tue, 2 May 2017 23:28:00 +0000 (23:28 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Tue, 2 May 2017 23:28:00 +0000 (23:28 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1793598 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/hpsf/VariantSupport.java
src/testcases/org/apache/poi/hpsf/TestVariantSupport.java

index b098db504c9653996e971a003727da11fd15ff05..8c351cc39a23c8665a2e2378c87a71ca05ba9590 100644 (file)
@@ -20,11 +20,15 @@ package org.apache.poi.hpsf;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.UnsupportedEncodingException;
+import java.math.BigInteger;
 import java.util.Date;
 import java.util.LinkedList;
 import java.util.List;
 
 import org.apache.poi.util.CodePageUtil;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.apache.poi.util.LittleEndianConsts;
 import org.apache.poi.util.POILogFactory;
 import org.apache.poi.util.POILogger;
 import org.apache.poi.util.Removal;
@@ -63,6 +67,8 @@ public class VariantSupport extends Variant {
      * been issued for.
      */
     private static List<Long> unsupportedMessage;
+    
+    private static final byte[] paddingBytes = new byte[3];
 
     
     /**
@@ -100,8 +106,9 @@ public class VariantSupport extends Variant {
         (final UnsupportedVariantTypeException ex) {
         if (isLogUnsupportedTypes())
         {
-            if (unsupportedMessage == null)
+            if (unsupportedMessage == null) {
                 unsupportedMessage = new LinkedList<Long>();
+            }
             Long vt = Long.valueOf(ex.getVariantType());
             if (!unsupportedMessage.contains(vt))
             {
@@ -124,9 +131,11 @@ public class VariantSupport extends Variant {
      *         {@code false}
      */
     public boolean isSupportedType(final int variantType) {
-        for (int i = 0; i < SUPPORTED_TYPES.length; i++)
-            if (variantType == SUPPORTED_TYPES[i])
+        for (int st : SUPPORTED_TYPES) {
+            if (variantType == st) {
                 return true;
+            }
+        }
         return false;
     }
 
@@ -152,14 +161,21 @@ public class VariantSupport extends Variant {
     public static Object read( final byte[] src, final int offset,
             final int length, final long type, final int codepage )
     throws ReadingNotSupportedException, UnsupportedEncodingException {
+        LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(src, offset);
+        return read( lei, length, type, codepage );
+    }
+        
+    public static Object read( LittleEndianByteArrayInputStream lei,
+            final int length, final long type, final int codepage )
+    throws ReadingNotSupportedException, UnsupportedEncodingException {
+        final int offset = lei.getReadIndex();
         TypedPropertyValue typedPropertyValue = new TypedPropertyValue( (int) type, null );
-        int unpadded;
         try {
-            unpadded = typedPropertyValue.readValue( src, offset );
+            typedPropertyValue.readValue(lei);
         } catch ( UnsupportedOperationException exc ) {
-            int propLength = Math.min( length, src.length - offset );
+            int propLength = Math.min( length, lei.available() );
             final byte[] v = new byte[propLength];
-            System.arraycopy( src, offset, v, 0, propLength );
+            lei.readFully(v, 0, propLength);
             throw new ReadingNotSupportedException( type, v );
         }
 
@@ -171,8 +187,14 @@ public class VariantSupport extends Variant {
              * changed -- sergey
              */
             case Variant.VT_EMPTY:
+            case Variant.VT_I1:
+            case Variant.VT_UI1:
+            case Variant.VT_UI2:
             case Variant.VT_I4:
+            case Variant.VT_UI4:
             case Variant.VT_I8:
+            case Variant.VT_UI8:
+            case Variant.VT_R4:
             case Variant.VT_R8:
                 return typedPropertyValue.getValue();
 
@@ -220,14 +242,16 @@ public class VariantSupport extends Variant {
             case Variant.VT_BOOL:
                 VariantBool bool = (VariantBool) typedPropertyValue.getValue();
                 return bool.getValue();
-
+                
             /*
              * it is not very good, but what can do without breaking current
              * API? --sergey
              */
             default:
+                final int unpadded = lei.getReadIndex()-offset;
+                lei.setReadIndex(offset);
                 final byte[] v = new byte[unpadded];
-                System.arraycopy( src, offset, v, 0, unpadded );
+                lei.readFully( v, 0, unpadded );
                 throw new ReadingNotSupportedException( type, v );
         }
     }
@@ -248,6 +272,7 @@ public class VariantSupport extends Variant {
      *
      * @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)}
      */
+    @Deprecated
     @Removal(version="3.18")
     public static String codepageToEncoding(final int codepage)
     throws UnsupportedEncodingException
@@ -260,11 +285,6 @@ public class VariantSupport extends Variant {
      * Writes a variant value to an output stream. This method ensures that
      * always a multiple of 4 bytes is written.<p>
      *
-     * If the codepage is UTF-16, which is encouraged, strings
-     * <strong>must</strong> always be written as {@link Variant#VT_LPWSTR}
-     * strings, not as {@link Variant#VT_LPSTR} strings. This method ensure this
-     * by converting strings appropriately, if needed.
-     *
      * @param out The stream to write the value to.
      * @param type The variant's type.
      * @param value The variant's value.
@@ -278,102 +298,151 @@ public class VariantSupport extends Variant {
     public static int write(final OutputStream out, final long type,
                             final Object value, final int codepage)
     throws IOException, WritingNotSupportedException {
-        int length = 0;
+        int length = -1;
         switch ((int) type) {
-            case Variant.VT_BOOL:
-                if ( ( (Boolean) value ).booleanValue() ) {
-                    out.write( 0xff );
-                    out.write( 0xff );
-                } else {
-                    out.write( 0x00 );
-                    out.write( 0x00 );
+            case Variant.VT_BOOL: {
+                if (value instanceof Boolean) {
+                    int bb = ((Boolean)value) ? 0xff : 0x00;
+                    out.write(bb);
+                    out.write(bb);
+                    length = 2;
                 }
-                length += 2;
                 break;
+            }
 
             case Variant.VT_LPSTR:
-                CodePageString codePageString = new CodePageString( (String) value, codepage );
-                length += codePageString.write( out );
+                if (value instanceof String) {
+                    CodePageString codePageString = new CodePageString();
+                    codePageString.setJavaValue( (String)value, codepage );
+                    length = codePageString.write( out );
+                }
                 break;
 
             case Variant.VT_LPWSTR:
-                final int nrOfChars = ( (String) value ).length() + 1;
-                length += TypeWriter.writeUIntToStream( out, nrOfChars );
-                for ( char s : ( (String) value ).toCharArray() ) {
-                    final int high = ( ( s & 0x0000ff00 ) >> 8 );
-                    final int low = ( s & 0x000000ff );
-                    final byte highb = (byte) high;
-                    final byte lowb = (byte) low;
-                    out.write( lowb );
-                    out.write( highb );
-                    length += 2;
+                if (value instanceof String) {
+                    UnicodeString uniString = new UnicodeString();
+                    uniString.setJavaValue((String)value);
+                    length = uniString.write(out);
                 }
-                // NullTerminator
-                out.write( 0x00 );
-                out.write( 0x00 );
-                length += 2;
                 break;
 
             case Variant.VT_CF:
-                final byte[] cf = (byte[]) value;
-                out.write(cf);
-                length = cf.length;
+                if (value instanceof byte[]) {
+                    final byte[] cf = (byte[]) value;
+                    out.write(cf);
+                    length = cf.length;
+                }
                 break;
 
             case Variant.VT_EMPTY:
-                length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY );
+                LittleEndian.putUInt(Variant.VT_EMPTY, out);
+                length = LittleEndianConsts.INT_SIZE;
                 break;
 
             case Variant.VT_I2:
-                length += TypeWriter.writeToStream( out, ( (Integer) value ).shortValue() );
+                if (value instanceof Number) {
+                    LittleEndian.putShort( out, ((Number)value).shortValue() );
+                    length = LittleEndianConsts.SHORT_SIZE;
+                }
+                break;
+
+            case Variant.VT_UI2:
+                if (value instanceof Number) {
+                    LittleEndian.putUShort( ((Number)value).intValue(), out );
+                    length = LittleEndianConsts.SHORT_SIZE;
+                }
                 break;
 
             case Variant.VT_I4:
-                if (!(value instanceof Integer)) {
-                    throw new ClassCastException("Could not cast an object to "
-                            + Integer.class + ": "
-                            + value.getClass() + ", "
-                            + value);
+                if (value instanceof Number) {
+                    LittleEndian.putInt( ((Number)value).intValue(), out);
+                    length = LittleEndianConsts.INT_SIZE;
+                }
+                break;
+
+            case Variant.VT_UI4:
+                if (value instanceof Number) {
+                    LittleEndian.putUInt( ((Number)value).longValue(), out);
+                    length = LittleEndianConsts.INT_SIZE;
                 }
-                length += TypeWriter.writeToStream(out, ((Integer) value).intValue());
                 break;
 
             case Variant.VT_I8:
-                length += TypeWriter.writeToStream(out, ((Long) value).longValue());
+                if (value instanceof Number) {
+                    LittleEndian.putLong( ((Number)value).longValue(), out);
+                    length = LittleEndianConsts.LONG_SIZE;
+                }
                 break;
 
+            case Variant.VT_UI8: {
+                if (value instanceof Number) {
+                    BigInteger bi = (value instanceof BigInteger) ? (BigInteger)value : BigInteger.valueOf(((Number)value).longValue());
+                    if (bi.bitLength() > 64) {
+                        throw new WritingNotSupportedException(type, value);
+                    }
+                    
+                    byte[] biBytesBE = bi.toByteArray(), biBytesLE = new byte[LittleEndianConsts.LONG_SIZE];
+                    int i=biBytesBE.length;
+                    for (byte b : biBytesBE) {
+                        if (i<=LittleEndianConsts.LONG_SIZE) {
+                            biBytesLE[i-1] = b;
+                        }
+                        i--;
+                    }
+    
+                    out.write(biBytesLE);
+                    length = LittleEndianConsts.LONG_SIZE;
+                }
+                break;
+            }
+
+            case Variant.VT_R4: {
+                if (value instanceof Number) {
+                    int floatBits = Float.floatToIntBits(((Number)value).floatValue());
+                    LittleEndian.putInt(floatBits, out);
+                    length = LittleEndianConsts.INT_SIZE;
+                }
+                break;
+            }
+            
             case Variant.VT_R8:
-                length += TypeWriter.writeToStream(out, ((Double) value).doubleValue());
+                if (value instanceof Number) {
+                    LittleEndian.putDouble( ((Number)value).doubleValue(), out);
+                    length = LittleEndianConsts.DOUBLE_SIZE;
+                }
                 break;
 
             case Variant.VT_FILETIME:
-                long filetime = Util.dateToFileTime((Date) value);
-                int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
-                int low = (int) (filetime & 0x00000000FFFFFFFFL);
-                Filetime filetimeValue = new Filetime( low, high);
-                length += filetimeValue.write( out );
+                if (value instanceof Date) {
+                    long filetime = Util.dateToFileTime((Date) value);
+                    int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
+                    int low = (int) (filetime & 0x00000000FFFFFFFFL);
+                    Filetime filetimeValue = new Filetime( low, high);
+                    length = filetimeValue.write( out );
+                }
                 break;
 
             default:
-                /* The variant type is not supported yet. However, if the value
-                 * is a byte array we can write it nevertheless. */
-                if (value instanceof byte[]) {
-                    final byte[] b = (byte[]) value;
-                    out.write(b);
-                    length = b.length;
-                    writeUnsupportedTypeMessage(new WritingNotSupportedException(type, value));
-                } else {
-                    throw new WritingNotSupportedException(type, value);
-                }
                 break;
         }
 
-        /* pad values to 4-bytes */
-        while ( ( length & 0x3 ) != 0 ) {
-            out.write( 0x00 );
-            length++;
+        /* The variant type is not supported yet. However, if the value
+         * is a byte array we can write it nevertheless. */
+        if (length == -1) {
+            if (value instanceof byte[]) {
+                final byte[] b = (byte[]) value;
+                out.write(b);
+                length = b.length;
+                writeUnsupportedTypeMessage(new WritingNotSupportedException(type, value));
+            } else {
+                throw new WritingNotSupportedException(type, value);
+            }
         }
+        
+        /* pad values to 4-bytes */
+        int padding = (4-(length & 0x3)) & 0x3;
+        out.write(paddingBytes, 0, padding);
 
-        return length;
+        return length + padding;
     }
 }
index 3b6354e4dace1362b1996193c1cde4e877a35041..ce8ba23c6e252f68477f291e76402f6f00018562 100644 (file)
  */
 package org.apache.poi.hpsf;
 
-import junit.framework.TestCase;
-import org.apache.poi.hpsf.wellknown.PropertyIDMap;
-import org.apache.poi.util.HexRead;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotNull;
 
 import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.math.BigInteger;
 
-/**
- * @author Yegor Kozlov
- */
-public class TestVariantSupport extends TestCase {
+import org.apache.poi.hpsf.wellknown.PropertyIDMap;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.poifs.storage.RawDataUtil;
+import org.apache.poi.util.LittleEndianByteArrayInputStream;
+import org.junit.Test;
 
+public class TestVariantSupport {
+    @Test
     public void test52337() throws Exception {
         // document summary stream   from test1-excel.doc attached to Bugzilla 52337
-        String hex =
-                "FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 00 00 00 00" +
-                "00 00 00 00 02 00 00 00 02 D5 CD D5 9C 2E 1B 10 93 97 08 00" +
-                "2B 2C F9 AE 44 00 00 00 05 D5 CD D5 9C 2E 1B 10 93 97 08 00" +
-                "2B 2C F9 AE 58 01 00 00 14 01 00 00 0C 00 00 00 01 00 00 00" +
-                "68 00 00 00 0F 00 00 00 70 00 00 00 05 00 00 00 98 00 00 00" +
-                "06 00 00 00 A0 00 00 00 11 00 00 00 A8 00 00 00 17 00 00 00" +
-                "B0 00 00 00 0B 00 00 00 B8 00 00 00 10 00 00 00 C0 00 00 00" +
-                "13 00 00 00 C8 00 00 00 16 00 00 00 D0 00 00 00 0D 00 00 00" +
-                "D8 00 00 00 0C 00 00 00 F3 00 00 00 02 00 00 00 E4 04 00 00" +
-                "1E 00 00 00 20 00 00 00 44 65 70 61 72 74 6D 65 6E 74 20 6F" +
-                "66 20 49 6E 74 65 72 6E 61 6C 20 41 66 66 61 69 72 73 00 00" +
-                "03 00 00 00 05 00 00 00 03 00 00 00 01 00 00 00 03 00 00 00" +
-                "47 03 00 00 03 00 00 00 0F 27 0B 00 0B 00 00 00 00 00 00 00" +
-                "0B 00 00 00 00 00 00 00 0B 00 00 00 00 00 00 00 0B 00 00 00" +
-                "00 00 00 00 1E 10 00 00 01 00 00 00 0F 00 00 00 54 68 69 73" +
-                "20 69 73 20 61 20 74 65 73 74 00 0C 10 00 00 02 00 00 00 1E" +
-                "00 00 00 06 00 00 00 54 69 74 6C 65 00 03 00 00 00 01 00 00" +
-                "00 00 00 00 A8 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00" +
-                "01 00 00 00 38 00 00 00 02 00 00 00 40 00 00 00 01 00 00 00" +
-                "02 00 00 00 0C 00 00 00 5F 50 49 44 5F 48 4C 49 4E 4B 53 00" +
-                "02 00 00 00 E4 04 00 00 41 00 00 00 60 00 00 00 06 00 00 00" +
-                "03 00 00 00 74 00 74 00 03 00 00 00 09 00 00 00 03 00 00 00" +
-                "00 00 00 00 03 00 00 00 05 00 00 00 1F 00 00 00 0C 00 00 00" +
-                "4E 00 6F 00 72 00 6D 00 61 00 6C 00 32 00 2E 00 78 00 6C 00" +
-                "73 00 00 00 1F 00 00 00 0A 00 00 00 53 00 68 00 65 00 65 00" +
-                "74 00 31 00 21 00 44 00 32 00 00 00 ";
-        byte[] bytes = HexRead.readFromString(hex);
+        String documentSummaryEnc =
+            "H4sIAAAAAAAAAG2RsUvDQBjFXxsraiuNKDoI8ZwclIJOjhYCGpQitINbzXChgTQtyQ3+Hw52cHB0E"+
+            "kdHRxfBpeAf4H/g5KK+M59Firn8eNx3d+++x31+AZVSGdOfrZTHz+Prxrp7eTWH7Z2PO5+1ylTtrA"+
+            "SskBrXKOiROhnavWREZskNWSK3ZI3ckyp5IC55JMvkiaySF7JIXlF4v0tPbzOAR1XE18MwM32dGjW"+
+            "IVJAanaVhoppRFMZZDjjSgyO9WT10Cq1vVX/uh/Txn3pucc7m6fTiXPEPldG5Qc0t2vEkXic2iZ5c"+
+            "JDkd8VFS3pcMBzIvS7buaeB3j06C1nF7krFJPRdz62M4rM7/8f3NtyE+LQyQoY8QCfbQwAU1l/UF0"+
+            "ubraA6DXWzC5x7gG6xzLtsAAgAA";
+        byte[] bytes = RawDataUtil.decompress(documentSummaryEnc);
 
         PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(bytes));
         DocumentSummaryInformation dsi = (DocumentSummaryInformation) ps;
         Section s = dsi.getSections().get(0);
 
         Object hdrs =  s.getProperty(PropertyIDMap.PID_HEADINGPAIR);
-
-        assertNotNull("PID_HEADINGPAIR was not found", hdrs);
+        assertNotNull(hdrs);
+        assertEquals(byte[].class, hdrs.getClass());
         
-        assertTrue("PID_HEADINGPAIR: expected byte[] but was "+ hdrs.getClass(), hdrs instanceof byte[]);
         // parse the value
         Vector v = new Vector((short)Variant.VT_VARIANT);
-        v.read((byte[])hdrs, 0);
+        LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream((byte[])hdrs, 0); 
+        v.read(lei);
 
         TypedPropertyValue[] items = v.getValues();
         assertEquals(2, items.length);
 
-        assertNotNull(items[0].getValue());
-        assertTrue("first item must be CodePageString but was "+ items[0].getValue().getClass(),
-                items[0].getValue() instanceof CodePageString);
-        assertNotNull(items[1].getValue());
-        assertTrue("second item must be Integer but was "+ items[1].getValue().getClass(),
-                items[1].getValue() instanceof Integer);
-        assertEquals(1, (int)(Integer)items[1].getValue());
+        Object cp = items[0].getValue();
+        assertNotNull(cp);
+        assertEquals(CodePageString.class, cp.getClass());
+        Object i = items[1].getValue();
+        assertNotNull(i);
+        assertEquals(Integer.class, i.getClass());
+        assertEquals(1, i);
 
     }
+    
+    @Test
+    public void newNumberTypes() throws Exception {
+        ClipboardData cd = new ClipboardData();
+        cd.setValue(new byte[10]);
+        
+        Object exp[][] = {
+            { Variant.VT_CF, cd.toByteArray() },
+            { Variant.VT_BOOL, true },
+            { Variant.VT_LPSTR, "codepagestring" },
+            { Variant.VT_LPWSTR, "widestring" },
+            { Variant.VT_I2, -1 }, // int, not short ... :(
+            { Variant.VT_UI2, 0xFFFF },
+            { Variant.VT_I4, -1 },
+            { Variant.VT_UI4, 0xFFFFFFFFL },
+            { Variant.VT_I8, -1L },
+            { Variant.VT_UI8, BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.TEN) },
+            { Variant.VT_R4, -999.99f },
+            { Variant.VT_R8, -999.99d },
+        };
+        
+        HSSFWorkbook wb = new HSSFWorkbook();
+        POIFSFileSystem poifs = new POIFSFileSystem();
+        DocumentSummaryInformation dsi = PropertySetFactory.newDocumentSummaryInformation();
+        CustomProperties cpList = new CustomProperties();
+        for (Object o[] : exp) {
+            int type = (Integer)o[0];
+            Property p = new Property(PropertyIDMap.PID_MAX+type, type, o[1]);
+            cpList.put("testprop"+type, new CustomProperty(p, "testprop"+type));
+            
+        }
+        dsi.setCustomProperties(cpList);
+        dsi.write(poifs.getRoot(), DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        poifs.writeFilesystem(bos);
+        poifs.close();
+        poifs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
+        dsi = (DocumentSummaryInformation)PropertySetFactory.create(poifs.getRoot(), DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+        cpList = dsi.getCustomProperties();
+        int i=0;
+        for (Object o[] : exp) {
+            Object obj = cpList.get("testprop"+o[0]);
+            if (o[1] instanceof byte[]) {
+                assertArrayEquals("property "+i, (byte[])o[1], (byte[])obj);
+            } else {
+                assertEquals("property "+i, o[1], obj);
+            }
+            i++;
+        }
+        poifs.close();
+    }
 }