diff --git a/src/java/org/apache/poi/hpsf/Array.java b/src/java/org/apache/poi/hpsf/Array.java new file mode 100644 index 000000000..cdb78f6f9 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Array.java @@ -0,0 +1,121 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/Blob.java b/src/java/org/apache/poi/hpsf/Blob.java new file mode 100644 index 000000000..a3421c2e0 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Blob.java @@ -0,0 +1,30 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/ClipboardData.java b/src/java/org/apache/poi/hpsf/ClipboardData.java new file mode 100644 index 000000000..477a40b95 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/ClipboardData.java @@ -0,0 +1,27 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/CodePageString.java b/src/java/org/apache/poi/hpsf/CodePageString.java new file mode 100644 index 000000000..c0e9de92e --- /dev/null +++ b/src/java/org/apache/poi/hpsf/CodePageString.java @@ -0,0 +1,31 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/Currency.java b/src/java/org/apache/poi/hpsf/Currency.java new file mode 100644 index 000000000..204cb91db --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Currency.java @@ -0,0 +1,18 @@ +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 ); + } +} diff --git a/src/java/org/apache/poi/hpsf/Date.java b/src/java/org/apache/poi/hpsf/Date.java new file mode 100644 index 000000000..ede62ee57 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Date.java @@ -0,0 +1,18 @@ +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 ); + } +} diff --git a/src/java/org/apache/poi/hpsf/Decimal.java b/src/java/org/apache/poi/hpsf/Decimal.java new file mode 100644 index 000000000..7572945f8 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Decimal.java @@ -0,0 +1,37 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/Filetime.java b/src/java/org/apache/poi/hpsf/Filetime.java new file mode 100644 index 000000000..99aa23bae --- /dev/null +++ b/src/java/org/apache/poi/hpsf/Filetime.java @@ -0,0 +1,17 @@ +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 ); + } +} diff --git a/src/java/org/apache/poi/hpsf/GUID.java b/src/java/org/apache/poi/hpsf/GUID.java new file mode 100644 index 000000000..050cb9327 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/GUID.java @@ -0,0 +1,24 @@ +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 ); + } +} diff --git a/src/java/org/apache/poi/hpsf/IndirectPropertyName.java b/src/java/org/apache/poi/hpsf/IndirectPropertyName.java new file mode 100644 index 000000000..826136ccd --- /dev/null +++ b/src/java/org/apache/poi/hpsf/IndirectPropertyName.java @@ -0,0 +1,19 @@ +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(); + } +} diff --git a/src/java/org/apache/poi/hpsf/TypedPropertyValue.java b/src/java/org/apache/poi/hpsf/TypedPropertyValue.java new file mode 100644 index 000000000..b421fcd48 --- /dev/null +++ b/src/java/org/apache/poi/hpsf/TypedPropertyValue.java @@ -0,0 +1,224 @@ +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 ) ); + } +} diff --git a/src/java/org/apache/poi/hpsf/UnicodeString.java b/src/java/org/apache/poi/hpsf/UnicodeString.java new file mode 100644 index 000000000..b2595d87d --- /dev/null +++ b/src/java/org/apache/poi/hpsf/UnicodeString.java @@ -0,0 +1,35 @@ +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; + } +} diff --git a/src/java/org/apache/poi/hpsf/Variant.java b/src/java/org/apache/poi/hpsf/Variant.java index a37cbf047..8330a6d5a 100644 --- a/src/java/org/apache/poi/hpsf/Variant.java +++ b/src/java/org/apache/poi/hpsf/Variant.java @@ -298,6 +298,14 @@ public class Variant */ 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] simple counted array. How long is this? How is it to be
diff --git a/src/java/org/apache/poi/hpsf/VariantBool.java b/src/java/org/apache/poi/hpsf/VariantBool.java
new file mode 100644
index 000000000..4342cdfdd
--- /dev/null
+++ b/src/java/org/apache/poi/hpsf/VariantBool.java
@@ -0,0 +1,32 @@
+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" );
+ }
+}
diff --git a/src/java/org/apache/poi/hpsf/VariantVector.java b/src/java/org/apache/poi/hpsf/VariantVector.java
new file mode 100644
index 000000000..7335c9712
--- /dev/null
+++ b/src/java/org/apache/poi/hpsf/VariantVector.java
@@ -0,0 +1,43 @@
+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