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 c22e972b3d
commit 57460325b4
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.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;
}
}

View File

@ -18,71 +18,107 @@
*/
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();
}
}