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;
* been issued for.
*/
private static List<Long> unsupportedMessage;
+
+ private static final byte[] paddingBytes = new byte[3];
/**
(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))
{
* {@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;
}
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 );
}
* 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();
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 );
}
}
*
* @deprecated POI 3.16 - use {@link CodePageUtil#codepageToEncoding(int)}
*/
+ @Deprecated
@Removal(version="3.18")
public static String codepageToEncoding(final int codepage)
throws UnsupportedEncodingException
* 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.
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;
}
}
*/
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();
+ }
}