--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Array
+{
+ static class ArrayDimension
+ {
+ static final int SIZE = 8;
+
+ private int _indexOffset;
+ private long _size;
+
+ ArrayDimension( byte[] data, int offset )
+ {
+ _size = LittleEndian.getUInt( data, offset );
+ _indexOffset = LittleEndian.getInt( data, offset
+ + LittleEndian.INT_SIZE );
+ }
+ }
+
+ static class ArrayHeader
+ {
+ private ArrayDimension[] _dimensions;
+ private int _type;
+
+ ArrayHeader( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ _type = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ long numDimensionsUnsigned = LittleEndian.getUInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ if ( !( 1 <= numDimensionsUnsigned && numDimensionsUnsigned <= 31 ) )
+ throw new IllegalPropertySetDataException(
+ "Array dimension number " + numDimensionsUnsigned
+ + " is not in [1; 31] range" );
+ int numDimensions = (int) numDimensionsUnsigned;
+
+ _dimensions = new ArrayDimension[numDimensions];
+ for ( int i = 0; i < numDimensions; i++ )
+ {
+ _dimensions[i] = new ArrayDimension( data, offset );
+ offset += ArrayDimension.SIZE;
+ }
+ }
+
+ long getNumberOfScalarValues()
+ {
+ long result = 1;
+ for ( ArrayDimension dimension : _dimensions )
+ result *= dimension._size;
+ return result;
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE * 2 + _dimensions.length
+ * ArrayDimension.SIZE;
+ }
+
+ int getType()
+ {
+ return _type;
+ }
+ }
+
+ private ArrayHeader _header;
+ private TypedPropertyValue[] _values;
+
+ Array()
+ {
+ }
+
+ Array( final byte[] data, final int offset )
+ {
+ read( data, offset );
+ }
+
+ int read( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ _header = new ArrayHeader( data, offset );
+ offset += _header.getSize();
+
+ long numberOfScalarsLong = _header.getNumberOfScalarValues();
+ if ( numberOfScalarsLong > Integer.MAX_VALUE )
+ throw new UnsupportedOperationException(
+ "Sorry, but POI can't store array of properties with size of "
+ + numberOfScalarsLong + " in memory" );
+ int numberOfScalars = (int) numberOfScalarsLong;
+
+ _values = new TypedPropertyValue[numberOfScalars];
+ final int type = _header._type;
+ if ( type == Variant.VT_VARIANT )
+ {
+ for ( int i = 0; i < numberOfScalars; i++ )
+ {
+ TypedPropertyValue typedPropertyValue = new TypedPropertyValue();
+ offset += typedPropertyValue.read( data, offset );
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < numberOfScalars; i++ )
+ {
+ TypedPropertyValue typedPropertyValue = new TypedPropertyValue(
+ type, null );
+ offset += typedPropertyValue.readValuePadded( data, offset );
+ }
+ }
+
+ return offset - startOffset;
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class Blob
+{
+ private byte[] _value;
+
+ Blob( byte[] data, int offset )
+ {
+ int size = LittleEndian.getInt( data, offset );
+
+ if ( size == 0 )
+ {
+ _value = new byte[0];
+ return;
+ }
+
+ _value = LittleEndian.getByteArray( _value, offset
+ + LittleEndian.INT_SIZE, size );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+class ClipboardData
+{
+ private int _format;
+ private byte[] _value;
+
+ ClipboardData( byte[] data, int offset )
+ {
+ int size = LittleEndian.getInt( data, offset );
+
+ if ( size < 4 )
+ throw new IllegalPropertySetDataException(
+ "ClipboardData size less than 4 bytes "
+ + "(doesn't even have format field!)" );
+ _format = LittleEndian.getInt( data, offset + LittleEndian.INT_SIZE );
+ _value = LittleEndian.getByteArray( data, offset
+ + LittleEndian.INT_SIZE * 2, size - LittleEndian.INT_SIZE );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE * 2 + _value.length;
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class CodePageString
+{
+
+ byte[] _value;
+
+ CodePageString( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ int size = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ _value = LittleEndian.getByteArray( data, offset, size );
+ if ( _value[size - 1] != 0 )
+ throw new IllegalPropertySetDataException(
+ "CodePageString started at offset #" + offset
+ + " is not NULL-terminated" );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class Currency
+{
+ static final int SIZE = 8;
+
+ private byte[] _value;
+
+ Currency( byte[] data, int offset )
+ {
+ _value = LittleEndian.getByteArray( data, offset, SIZE );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class Date
+{
+ static final int SIZE = 8;
+
+ private byte[] _value;
+
+ Date( byte[] data, int offset )
+ {
+ _value = LittleEndian.getByteArray( data, offset, SIZE );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class Decimal
+{
+ static final int SIZE = 16;
+
+ private short field_1_wReserved;
+ private byte field_2_scale;
+ private byte field_3_sign;
+ private int field_4_hi32;
+ private long field_5_lo64;
+
+ Decimal( final byte[] data, final int startOffset )
+ {
+ int offset = startOffset;
+
+ field_1_wReserved = LittleEndian.getShort( data, offset );
+ offset += LittleEndian.SHORT_SIZE;
+
+ field_2_scale = data[offset];
+ offset += LittleEndian.BYTE_SIZE;
+
+ field_3_sign = data[offset];
+ offset += LittleEndian.BYTE_SIZE;
+
+ field_4_hi32 = LittleEndian.getInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ field_5_lo64 = LittleEndian.getLong( data, offset );
+ offset += LittleEndian.LONG_SIZE;
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+class Filetime
+{
+ static final int SIZE = LittleEndian.LONG_SIZE * 2;
+
+ private long _dwLowDateTime;
+ private long _dwHighDateTime;
+
+ Filetime( byte[] data, int offset )
+ {
+ _dwLowDateTime = LittleEndian.getLong( data, offset + 0 );
+ _dwHighDateTime = LittleEndian.getLong( data, offset + 4 );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class GUID
+{
+ static final int SIZE = 16;
+
+ private int _data1;
+ private short _data2;
+ private short _data3;
+ private long _data4;
+
+ GUID( byte[] data, int offset )
+ {
+ _data1 = LittleEndian.getInt( data, offset + 0 );
+ _data2 = LittleEndian.getShort( data, offset + 4 );
+ _data3 = LittleEndian.getShort( data, offset + 6 );
+ _data4 = LittleEndian.getLong( data, offset + 8 );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class IndirectPropertyName
+{
+ private CodePageString _value;
+
+ IndirectPropertyName( byte[] data, int offset )
+ {
+ _value = new CodePageString( data, offset );
+ }
+
+ int getSize()
+ {
+ return _value.getSize();
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class TypedPropertyValue
+{
+ private int _type;
+
+ private Object _value;
+
+ TypedPropertyValue()
+ {
+ }
+
+ TypedPropertyValue( byte[] data, int startOffset )
+ {
+ read( data, startOffset );
+ }
+
+ TypedPropertyValue( int type, Object value )
+ {
+ _type = type;
+ _value = value;
+ }
+
+ int read( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ _type = LittleEndian.getShort( data, offset );
+ offset += LittleEndian.SHORT_SIZE;
+
+ short padding = LittleEndian.getShort( data, offset );
+ if ( padding != 0 )
+ throw new IllegalPropertySetDataException(
+ "Property padding at offset " + offset
+ + " MUST be 0, but it's value is " + padding );
+ offset += LittleEndian.SHORT_SIZE;
+ offset += readValuePadded( data, offset );
+
+ return offset - startOffset;
+ }
+
+ int readValue( byte[] data, int offset )
+ {
+ switch ( _type )
+ {
+ case Variant.VT_EMPTY:
+ case Variant.VT_NULL:
+ _value = null;
+ return 0;
+
+ case Variant.VT_I2:
+ _value = Short.valueOf( LittleEndian.getShort( data, offset ) );
+ return 4;
+
+ case Variant.VT_I4:
+ _value = Integer.valueOf( LittleEndian.getInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_R4:
+ _value = Short.valueOf( LittleEndian.getShort( data, offset ) );
+ return 4;
+
+ case Variant.VT_R8:
+ _value = Double.valueOf( LittleEndian.getDouble( data, offset ) );
+ return 8;
+
+ case Variant.VT_CY:
+ _value = new Currency( data, offset );
+ return Currency.SIZE;
+
+ case Variant.VT_DATE:
+ _value = new Date( data, offset );
+ return Date.SIZE;
+
+ case Variant.VT_BSTR:
+ _value = new CodePageString( data, offset );
+ return ( (CodePageString) _value ).getSize();
+
+ case Variant.VT_ERROR:
+ _value = Long.valueOf( LittleEndian.getUInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_BOOL:
+ _value = new VariantBool( data, offset );
+ return VariantBool.SIZE;
+
+ case Variant.VT_DECIMAL:
+ _value = new Decimal( data, offset );
+ return Decimal.SIZE;
+
+ case Variant.VT_I1:
+ _value = Byte.valueOf( data[offset] );
+ return 1;
+
+ case Variant.VT_UI1:
+ _value = Short.valueOf( LittleEndian.getUByte( data, offset ) );
+ return 2;
+
+ case Variant.VT_UI2:
+ _value = Integer.valueOf( LittleEndian.getUShort( data, offset ) );
+ return 4;
+
+ case Variant.VT_UI4:
+ _value = Long.valueOf( LittleEndian.getUInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_I8:
+ _value = Long.valueOf( LittleEndian.getLong( data, offset ) );
+ return 8;
+
+ case Variant.VT_UI8:
+ _value = LittleEndian.getByteArray( data, offset, 8 );
+ return 8;
+
+ case Variant.VT_INT:
+ _value = Integer.valueOf( LittleEndian.getInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_UINT:
+ _value = Long.valueOf( LittleEndian.getUInt( data, offset ) );
+ return 4;
+
+ case Variant.VT_LPSTR:
+ _value = new CodePageString( data, offset );
+ return ( (CodePageString) _value ).getSize();
+
+ case Variant.VT_LPWSTR:
+ _value = new UnicodeString( data, offset );
+ return ( (UnicodeString) _value ).getSize();
+
+ case Variant.VT_FILETIME:
+ _value = new Filetime( data, offset );
+ return Filetime.SIZE;
+
+ case Variant.VT_BLOB:
+ _value = new Blob( data, offset );
+ return ( (Blob) _value ).getSize();
+
+ case Variant.VT_STREAM:
+ case Variant.VT_STORAGE:
+ case Variant.VT_STREAMED_OBJECT:
+ case Variant.VT_STORED_OBJECT:
+ _value = new IndirectPropertyName( data, offset );
+ return ( (IndirectPropertyName) _value ).getSize();
+
+ case Variant.VT_BLOB_OBJECT:
+ _value = new Blob( data, offset );
+ return ( (Blob) _value ).getSize();
+
+ case Variant.VT_CF:
+ _value = new ClipboardData( data, offset );
+ return ( (ClipboardData) _value ).getSize();
+
+ case Variant.VT_CLSID:
+ _value = new GUID( data, offset );
+ return GUID.SIZE;
+
+ case Variant.VT_VERSIONED_STREAM:
+ _value = new VersionedStream( data, offset );
+ return ( (VersionedStream) _value ).getSize();
+
+ case Variant.VT_VECTOR | Variant.VT_I2:
+ case Variant.VT_VECTOR | Variant.VT_I4:
+ case Variant.VT_VECTOR | Variant.VT_R4:
+ case Variant.VT_VECTOR | Variant.VT_R8:
+ case Variant.VT_VECTOR | Variant.VT_CY:
+ case Variant.VT_VECTOR | Variant.VT_DATE:
+ case Variant.VT_VECTOR | Variant.VT_BSTR:
+ case Variant.VT_VECTOR | Variant.VT_ERROR:
+ case Variant.VT_VECTOR | Variant.VT_BOOL:
+ case Variant.VT_VECTOR | Variant.VT_VARIANT:
+ case Variant.VT_VECTOR | Variant.VT_I1:
+ case Variant.VT_VECTOR | Variant.VT_UI1:
+ case Variant.VT_VECTOR | Variant.VT_UI2:
+ case Variant.VT_VECTOR | Variant.VT_UI4:
+ case Variant.VT_VECTOR | Variant.VT_I8:
+ case Variant.VT_VECTOR | Variant.VT_UI8:
+ case Variant.VT_VECTOR | Variant.VT_LPSTR:
+ case Variant.VT_VECTOR | Variant.VT_LPWSTR:
+ case Variant.VT_VECTOR | Variant.VT_FILETIME:
+ case Variant.VT_VECTOR | Variant.VT_CF:
+ case Variant.VT_VECTOR | Variant.VT_CLSID:
+ _value = new Vector( (short) ( _type & 0x0FFF ) );
+ return ( (Vector) _value ).read( data, offset );
+
+ case Variant.VT_ARRAY | Variant.VT_I2:
+ case Variant.VT_ARRAY | Variant.VT_I4:
+ case Variant.VT_ARRAY | Variant.VT_R4:
+ case Variant.VT_ARRAY | Variant.VT_R8:
+ case Variant.VT_ARRAY | Variant.VT_CY:
+ case Variant.VT_ARRAY | Variant.VT_DATE:
+ case Variant.VT_ARRAY | Variant.VT_BSTR:
+ case Variant.VT_ARRAY | Variant.VT_ERROR:
+ case Variant.VT_ARRAY | Variant.VT_BOOL:
+ case Variant.VT_ARRAY | Variant.VT_VARIANT:
+ case Variant.VT_ARRAY | Variant.VT_DECIMAL:
+ case Variant.VT_ARRAY | Variant.VT_I1:
+ case Variant.VT_ARRAY | Variant.VT_UI1:
+ case Variant.VT_ARRAY | Variant.VT_UI2:
+ case Variant.VT_ARRAY | Variant.VT_UI4:
+ case Variant.VT_ARRAY | Variant.VT_INT:
+ case Variant.VT_ARRAY | Variant.VT_UINT:
+ _value = new Array();
+ return ( (Array) _value ).read( data, offset );
+
+ default:
+ throw new UnsupportedOperationException(
+ "Unknown (possibly, incorrect) TypedPropertyValue type: "
+ + _type );
+ }
+ }
+
+ int readValuePadded( byte[] data, int offset )
+ {
+ int nonPadded = readValue( data, offset );
+ return ( nonPadded & 0x03 ) == 0 ? nonPadded : nonPadded
+ + ( 4 - ( nonPadded & 0x03 ) );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+@Internal
+class UnicodeString
+{
+ private byte[] _value;
+
+ UnicodeString( byte[] data, int offset )
+ {
+ int length = LittleEndian.getInt( data, offset );
+
+ if ( length == 0 )
+ {
+ _value = new byte[0];
+ return;
+ }
+
+ _value = new byte[length * 2];
+ LittleEndian.getByteArray( data, offset + LittleEndian.INT_SIZE,
+ length * 2 );
+
+ if ( _value[length * 2 - 1] != 0 || _value[length * 2 - 2] != 0 )
+ throw new IllegalPropertySetDataException(
+ "UnicodeString started at offset #" + offset
+ + " is not NULL-terminated" );
+ }
+
+ int getSize()
+ {
+ return LittleEndian.INT_SIZE + _value.length;
+ }
+}
*/
public static final int VT_CLSID = 72;
+ /**
+ * "MUST be a VersionedStream. The storage representing the (non-simple)
+ * property set MUST have a stream element with the name in the StreamName
+ * field." -- [MS-OLEPS] -- v20110920; Object Linking and Embedding (OLE)
+ * Property Set Data Structures; page 24 / 63
+ */
+ public static final int VT_VERSIONED_STREAM = 0x0049;
+
/**
* <p>[P] simple counted array. <span style="background-color:
* #ffff00">How long is this? How is it to be
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.LittleEndian;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class VariantBool
+{
+ static final int SIZE = 2;
+
+ private boolean _value;
+
+ VariantBool( byte[] data, int offset )
+ {
+ short value = LittleEndian.getShort( data, offset );
+ if ( value == 0x0000 )
+ {
+ _value = false;
+ return;
+ }
+
+ if ( value == 0xffff )
+ {
+ _value = true;
+ return;
+ }
+
+ throw new IllegalPropertySetDataException( "VARIANT_BOOL value '"
+ + value + "' is incorrect" );
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Holder for vector-type properties
+ *
+ * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
+ */
+public class VariantVector
+{
+
+ private final List<Property> values;
+
+ public VariantVector( int codepage, long id, long type, byte[] data, int startOffset )
+ throws UnsupportedEncodingException, ReadingNotSupportedException
+ {
+ if ( ( type & 0x1000 ) != 0x1000 )
+ throw new IllegalArgumentException( "Specified type is not vector" );
+ final long elementType = type ^ 0x1000;
+
+ int offset = startOffset;
+
+ final long longLength = LittleEndian.getUInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ if ( longLength > Integer.MAX_VALUE )
+ throw new UnsupportedOperationException( "Vector is too long -- "
+ + longLength );
+ final int length = (int) longLength;
+
+ this.values = new ArrayList<Property>();
+ for ( int i = 0; i < length; i++ )
+ {
+ Property property = new Property( id, elementType, null );
+ VariantSupport.read( data, offset, length, elementType, codepage );
+ }
+ }
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * Holder for vector-type properties
+ *
+ * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
+ */
+@Internal
+class Vector
+{
+ private final short _type;
+
+ private TypedPropertyValue[] _values;
+
+ Vector( short type )
+ {
+ this._type = type;
+ }
+
+ Vector( byte[] data, int startOffset, short type )
+ {
+ this._type = type;
+ read( data, startOffset );
+ }
+
+ int read( byte[] data, int startOffset )
+ {
+ int offset = startOffset;
+
+ final long longLength = LittleEndian.getUInt( data, offset );
+ offset += LittleEndian.INT_SIZE;
+
+ if ( longLength > Integer.MAX_VALUE )
+ throw new UnsupportedOperationException( "Vector is too long -- "
+ + longLength );
+ final int length = (int) longLength;
+
+ _values = new TypedPropertyValue[length];
+
+ if ( _type == Variant.VT_VARIANT )
+ {
+ for ( int i = 0; i < length; i++ )
+ {
+ TypedPropertyValue value = new TypedPropertyValue();
+ offset += value.read( data, offset );
+ _values[i] = value;
+ }
+ }
+ else
+ {
+ for ( int i = 0; i < length; i++ )
+ {
+ TypedPropertyValue value = new TypedPropertyValue( _type, null );
+ offset += value.readValuePadded( data, offset );
+ _values[i] = value;
+ }
+ }
+ return offset - startOffset;
+ }
+
+}
--- /dev/null
+package org.apache.poi.hpsf;
+
+import org.apache.poi.util.Internal;
+
+@Internal
+class VersionedStream
+{
+ private GUID _versionGuid;
+ private IndirectPropertyName _streamName;
+
+ VersionedStream( byte[] data, int offset )
+ {
+ _versionGuid = new GUID( data, offset );
+ _streamName = new IndirectPropertyName( data, offset + GUID.SIZE );
+ }
+
+ int getSize()
+ {
+ return GUID.SIZE + _streamName.getSize();
+ }
+}