HPSF: Enable new number types in VariantSupport

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1793598 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2017-05-02 23:28:00 +00:00
parent 456b33a837
commit 3a1771ced1
2 changed files with 223 additions and 118 deletions

View File

@ -20,11 +20,15 @@ package org.apache.poi.hpsf;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.util.Date; import java.util.Date;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import org.apache.poi.util.CodePageUtil; 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.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
import org.apache.poi.util.Removal; import org.apache.poi.util.Removal;
@ -64,6 +68,8 @@ public class VariantSupport extends Variant {
*/ */
private static List<Long> unsupportedMessage; private static List<Long> unsupportedMessage;
private static final byte[] paddingBytes = new byte[3];
/** /**
* Specifies whether warnings about unsupported variant types are to be * Specifies whether warnings about unsupported variant types are to be
@ -100,8 +106,9 @@ public class VariantSupport extends Variant {
(final UnsupportedVariantTypeException ex) { (final UnsupportedVariantTypeException ex) {
if (isLogUnsupportedTypes()) if (isLogUnsupportedTypes())
{ {
if (unsupportedMessage == null) if (unsupportedMessage == null) {
unsupportedMessage = new LinkedList<Long>(); unsupportedMessage = new LinkedList<Long>();
}
Long vt = Long.valueOf(ex.getVariantType()); Long vt = Long.valueOf(ex.getVariantType());
if (!unsupportedMessage.contains(vt)) if (!unsupportedMessage.contains(vt))
{ {
@ -124,9 +131,11 @@ public class VariantSupport extends Variant {
* {@code false} * {@code false}
*/ */
public boolean isSupportedType(final int variantType) { public boolean isSupportedType(final int variantType) {
for (int i = 0; i < SUPPORTED_TYPES.length; i++) for (int st : SUPPORTED_TYPES) {
if (variantType == SUPPORTED_TYPES[i]) if (variantType == st) {
return true; return true;
}
}
return false; return false;
} }
@ -152,14 +161,21 @@ public class VariantSupport extends Variant {
public static Object read( final byte[] src, final int offset, public static Object read( final byte[] src, final int offset,
final int length, final long type, final int codepage ) final int length, final long type, final int codepage )
throws ReadingNotSupportedException, UnsupportedEncodingException { 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 ); TypedPropertyValue typedPropertyValue = new TypedPropertyValue( (int) type, null );
int unpadded;
try { try {
unpadded = typedPropertyValue.readValue( src, offset ); typedPropertyValue.readValue(lei);
} catch ( UnsupportedOperationException exc ) { } catch ( UnsupportedOperationException exc ) {
int propLength = Math.min( length, src.length - offset ); int propLength = Math.min( length, lei.available() );
final byte[] v = new byte[propLength]; final byte[] v = new byte[propLength];
System.arraycopy( src, offset, v, 0, propLength ); lei.readFully(v, 0, propLength);
throw new ReadingNotSupportedException( type, v ); throw new ReadingNotSupportedException( type, v );
} }
@ -171,8 +187,14 @@ public class VariantSupport extends Variant {
* changed -- sergey * changed -- sergey
*/ */
case Variant.VT_EMPTY: case Variant.VT_EMPTY:
case Variant.VT_I1:
case Variant.VT_UI1:
case Variant.VT_UI2:
case Variant.VT_I4: case Variant.VT_I4:
case Variant.VT_UI4:
case Variant.VT_I8: case Variant.VT_I8:
case Variant.VT_UI8:
case Variant.VT_R4:
case Variant.VT_R8: case Variant.VT_R8:
return typedPropertyValue.getValue(); return typedPropertyValue.getValue();
@ -226,8 +248,10 @@ public class VariantSupport extends Variant {
* API? --sergey * API? --sergey
*/ */
default: default:
final int unpadded = lei.getReadIndex()-offset;
lei.setReadIndex(offset);
final byte[] v = new byte[unpadded]; final byte[] v = new byte[unpadded];
System.arraycopy( src, offset, v, 0, unpadded ); lei.readFully( v, 0, unpadded );
throw new ReadingNotSupportedException( type, v ); throw new ReadingNotSupportedException( type, v );
} }
} }
@ -248,6 +272,7 @@ public class VariantSupport extends Variant {
* *
* @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)} * @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)}
*/ */
@Deprecated
@Removal(version="3.18") @Removal(version="3.18")
public static String codepageToEncoding(final int codepage) public static String codepageToEncoding(final int codepage)
throws UnsupportedEncodingException throws UnsupportedEncodingException
@ -260,11 +285,6 @@ public class VariantSupport extends Variant {
* Writes a variant value to an output stream. This method ensures that * Writes a variant value to an output stream. This method ensures that
* always a multiple of 4 bytes is written.<p> * 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 out The stream to write the value to.
* @param type The variant's type. * @param type The variant's type.
* @param value The variant's value. * @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, public static int write(final OutputStream out, final long type,
final Object value, final int codepage) final Object value, final int codepage)
throws IOException, WritingNotSupportedException { throws IOException, WritingNotSupportedException {
int length = 0; int length = -1;
switch ((int) type) { switch ((int) type) {
case Variant.VT_BOOL: case Variant.VT_BOOL: {
if ( ( (Boolean) value ).booleanValue() ) { if (value instanceof Boolean) {
out.write( 0xff ); int bb = ((Boolean)value) ? 0xff : 0x00;
out.write( 0xff ); out.write(bb);
} else { out.write(bb);
out.write( 0x00 ); length = 2;
out.write( 0x00 );
} }
length += 2;
break; break;
}
case Variant.VT_LPSTR: case Variant.VT_LPSTR:
CodePageString codePageString = new CodePageString( (String) value, codepage ); if (value instanceof String) {
length += codePageString.write( out ); CodePageString codePageString = new CodePageString();
codePageString.setJavaValue( (String)value, codepage );
length = codePageString.write( out );
}
break; break;
case Variant.VT_LPWSTR: case Variant.VT_LPWSTR:
final int nrOfChars = ( (String) value ).length() + 1; if (value instanceof String) {
length += TypeWriter.writeUIntToStream( out, nrOfChars ); UnicodeString uniString = new UnicodeString();
for ( char s : ( (String) value ).toCharArray() ) { uniString.setJavaValue((String)value);
final int high = ( ( s & 0x0000ff00 ) >> 8 ); length = uniString.write(out);
final int low = ( s & 0x000000ff );
final byte highb = (byte) high;
final byte lowb = (byte) low;
out.write( lowb );
out.write( highb );
length += 2;
} }
// NullTerminator
out.write( 0x00 );
out.write( 0x00 );
length += 2;
break; break;
case Variant.VT_CF: case Variant.VT_CF:
final byte[] cf = (byte[]) value; if (value instanceof byte[]) {
out.write(cf); final byte[] cf = (byte[]) value;
length = cf.length; out.write(cf);
length = cf.length;
}
break; break;
case Variant.VT_EMPTY: case Variant.VT_EMPTY:
length += TypeWriter.writeUIntToStream( out, Variant.VT_EMPTY ); LittleEndian.putUInt(Variant.VT_EMPTY, out);
length = LittleEndianConsts.INT_SIZE;
break; break;
case Variant.VT_I2: 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; break;
case Variant.VT_I4: case Variant.VT_I4:
if (!(value instanceof Integer)) { if (value instanceof Number) {
throw new ClassCastException("Could not cast an object to " LittleEndian.putInt( ((Number)value).intValue(), out);
+ Integer.class + ": " length = LittleEndianConsts.INT_SIZE;
+ value.getClass() + ", " }
+ value); 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; break;
case Variant.VT_I8: 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; 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: 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; break;
case Variant.VT_FILETIME: case Variant.VT_FILETIME:
long filetime = Util.dateToFileTime((Date) value); if (value instanceof Date) {
int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL); long filetime = Util.dateToFileTime((Date) value);
int low = (int) (filetime & 0x00000000FFFFFFFFL); int high = (int) ((filetime >> 32) & 0x00000000FFFFFFFFL);
Filetime filetimeValue = new Filetime( low, high); int low = (int) (filetime & 0x00000000FFFFFFFFL);
length += filetimeValue.write( out ); Filetime filetimeValue = new Filetime( low, high);
length = filetimeValue.write( out );
}
break; break;
default: 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; break;
} }
/* pad values to 4-bytes */ /* The variant type is not supported yet. However, if the value
while ( ( length & 0x3 ) != 0 ) { * is a byte array we can write it nevertheless. */
out.write( 0x00 ); if (length == -1) {
length++; 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);
}
} }
return length; /* pad values to 4-bytes */
int padding = (4-(length & 0x3)) & 0x3;
out.write(paddingBytes, 0, padding);
return length + padding;
} }
} }

View File

@ -18,71 +18,107 @@
*/ */
package org.apache.poi.hpsf; package org.apache.poi.hpsf;
import junit.framework.TestCase; import static org.junit.Assert.*;
import org.apache.poi.hpsf.wellknown.PropertyIDMap; import static org.junit.Assert.assertNotNull;
import org.apache.poi.util.HexRead;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
/** import org.apache.poi.hpsf.wellknown.PropertyIDMap;
* @author Yegor Kozlov import org.apache.poi.hssf.usermodel.HSSFWorkbook;
*/ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
public class TestVariantSupport extends TestCase { 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 { public void test52337() throws Exception {
// document summary stream from test1-excel.doc attached to Bugzilla 52337 // document summary stream from test1-excel.doc attached to Bugzilla 52337
String hex = String documentSummaryEnc =
"FE FF 00 00 05 01 02 00 00 00 00 00 00 00 00 00 00 00 00 00" + "H4sIAAAAAAAAAG2RsUvDQBjFXxsraiuNKDoI8ZwclIJOjhYCGpQitINbzXChgTQtyQ3+Hw52cHB0E"+
"00 00 00 00 02 00 00 00 02 D5 CD D5 9C 2E 1B 10 93 97 08 00" + "kdHRxfBpeAf4H/g5KK+M59Firn8eNx3d+++x31+AZVSGdOfrZTHz+Prxrp7eTWH7Z2PO5+1ylTtrA"+
"2B 2C F9 AE 44 00 00 00 05 D5 CD D5 9C 2E 1B 10 93 97 08 00" + "SskBrXKOiROhnavWREZskNWSK3ZI3ckyp5IC55JMvkiaySF7JIXlF4v0tPbzOAR1XE18MwM32dGjW"+
"2B 2C F9 AE 58 01 00 00 14 01 00 00 0C 00 00 00 01 00 00 00" + "IVJAanaVhoppRFMZZDjjSgyO9WT10Cq1vVX/uh/Txn3pucc7m6fTiXPEPldG5Qc0t2vEkXic2iZ5c"+
"68 00 00 00 0F 00 00 00 70 00 00 00 05 00 00 00 98 00 00 00" + "JDkd8VFS3pcMBzIvS7buaeB3j06C1nF7krFJPRdz62M4rM7/8f3NtyE+LQyQoY8QCfbQwAU1l/UF0"+
"06 00 00 00 A0 00 00 00 11 00 00 00 A8 00 00 00 17 00 00 00" + "ubraA6DXWzC5x7gG6xzLtsAAgAA";
"B0 00 00 00 0B 00 00 00 B8 00 00 00 10 00 00 00 C0 00 00 00" + byte[] bytes = RawDataUtil.decompress(documentSummaryEnc);
"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);
PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(bytes)); PropertySet ps = PropertySetFactory.create(new ByteArrayInputStream(bytes));
DocumentSummaryInformation dsi = (DocumentSummaryInformation) ps; DocumentSummaryInformation dsi = (DocumentSummaryInformation) ps;
Section s = dsi.getSections().get(0); Section s = dsi.getSections().get(0);
Object hdrs = s.getProperty(PropertyIDMap.PID_HEADINGPAIR); Object hdrs = s.getProperty(PropertyIDMap.PID_HEADINGPAIR);
assertNotNull(hdrs);
assertEquals(byte[].class, hdrs.getClass());
assertNotNull("PID_HEADINGPAIR was not found", hdrs);
assertTrue("PID_HEADINGPAIR: expected byte[] but was "+ hdrs.getClass(), hdrs instanceof byte[]);
// parse the value // parse the value
Vector v = new Vector((short)Variant.VT_VARIANT); 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(); TypedPropertyValue[] items = v.getValues();
assertEquals(2, items.length); assertEquals(2, items.length);
assertNotNull(items[0].getValue()); Object cp = items[0].getValue();
assertTrue("first item must be CodePageString but was "+ items[0].getValue().getClass(), assertNotNull(cp);
items[0].getValue() instanceof CodePageString); assertEquals(CodePageString.class, cp.getClass());
assertNotNull(items[1].getValue()); Object i = items[1].getValue();
assertTrue("second item must be Integer but was "+ items[1].getValue().getClass(), assertNotNull(i);
items[1].getValue() instanceof Integer); assertEquals(Integer.class, i.getClass());
assertEquals(1, (int)(Integer)items[1].getValue()); 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();
}
} }