123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- /* ====================================================================
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- ==================================================================== */
-
- package org.apache.poi.hpsf;
-
- import static org.apache.logging.log4j.util.Unbox.box;
-
- import java.io.IOException;
- import java.io.OutputStream;
- import java.io.UnsupportedEncodingException;
- import java.nio.charset.Charset;
- import java.text.DateFormat;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- import java.util.Locale;
- import java.util.Objects;
- import java.util.concurrent.TimeUnit;
-
- import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
- import org.apache.logging.log4j.LogManager;
- import org.apache.logging.log4j.Logger;
- import org.apache.poi.hpsf.wellknown.PropertyIDMap;
- import org.apache.poi.util.CodePageUtil;
- import org.apache.poi.util.HexDump;
- import org.apache.poi.util.LittleEndian;
- import org.apache.poi.util.LittleEndianByteArrayInputStream;
- import org.apache.poi.util.LittleEndianConsts;
- import org.apache.poi.util.LocaleUtil;
-
- /**
- * A property in a {@link Section} of a {@link PropertySet}.<p>
- *
- * The property's {@code ID} gives the property a meaning
- * in the context of its {@link Section}. Each {@link Section} spans
- * its own name space of property IDs.<p>
- *
- * The property's {@code type} determines how its
- * {@code value} is interpreted. For example, if the type is
- * {@link Variant#VT_LPSTR} (byte string), the value consists of a
- * DWord telling how many bytes the string contains. The bytes follow
- * immediately, including any null bytes that terminate the
- * string. The type {@link Variant#VT_I4} denotes a four-byte integer
- * value, {@link Variant#VT_FILETIME} some date and time (of a file).<p>
- *
- * Please note that not all {@link Variant} types yet. This might change
- * over time but largely depends on your feedback so that the POI team knows
- * which variant types are really needed. So please feel free to submit error
- * reports or patches for the types you need.
- *
- * @see Section
- * @see Variant
- * @see <a href="https://msdn.microsoft.com/en-us/library/dd942421.aspx">
- * [MS-OLEPS]: Object Linking and Embedding (OLE) Property Set Data Structures</a>
- */
- public class Property {
-
- /**
- * Default codepage for {@link CodePageString CodePageStrings}
- */
- public static final int DEFAULT_CODEPAGE = CodePageUtil.CP_WINDOWS_1252;
-
- private static final Logger LOG = LogManager.getLogger(Property.class);
-
- /** The property's ID. */
- private long id;
-
- /** The property's type. */
- private long type;
-
- /** The property's value. */
- private Object value;
-
-
- /**
- * Creates an empty property. It must be filled using the set method to be usable.
- */
- public Property() {
- }
-
- /**
- * Creates a {@code Property} as a copy of an existing {@code Property}.
- *
- * @param p The property to copy.
- */
- public Property(Property p) {
- this(p.id, p.type, p.value);
- }
-
- /**
- * Creates a property.
- *
- * @param id the property's ID.
- * @param type the property's type, see {@link Variant}.
- * @param value the property's value. Only certain types are allowed, see
- * {@link Variant}.
- */
- public Property(final long id, final long type, final Object value) {
- this.id = id;
- this.type = type;
- this.value = value;
- }
-
- /**
- * Creates a Property instance by reading its bytes
- * from the property set stream.
- *
- * @param id The property's ID.
- * @param src The bytes the property set stream consists of.
- * @param offset The property's type/value pair's offset in the
- * section.
- * @param length The property's type/value pair's length in bytes.
- * @param codepage The section's and thus the property's
- * codepage. It is needed only when reading string values.
- * @throws UnsupportedEncodingException if the specified codepage is not
- * supported.
- */
- public Property(final long id, final byte[] src, final long offset, final int length, final int codepage)
- throws UnsupportedEncodingException {
- this.id = id;
-
- /*
- * ID 0 is a special case since it specifies a dictionary of
- * property IDs and property names.
- */
- if (id == 0) {
- throw new UnsupportedEncodingException("Dictionary not allowed here");
- }
-
- int o = (int) offset;
- type = LittleEndian.getUInt(src, o);
- o += LittleEndianConsts.INT_SIZE;
-
- try {
- value = VariantSupport.read(src, o, length, (int) type, codepage);
- } catch (UnsupportedVariantTypeException ex) {
- VariantSupport.writeUnsupportedTypeMessage(ex);
- value = ex.getValue();
- }
- }
-
- /**
- * Creates a Property instance by reading its bytes
- * from the property set stream.
- *
- * @param id The property's ID.
- * @param leis The bytes the property set stream consists of.
- * @param length The property's type/value pair's length in bytes.
- * @param codepage The section's and thus the property's
- * codepage. It is needed only when reading string values.
- * @throws UnsupportedEncodingException if the specified codepage is not
- * supported.
- */
- public Property(final long id, LittleEndianByteArrayInputStream leis, final int length, final int codepage)
- throws UnsupportedEncodingException {
- this.id = id;
-
- /*
- * ID 0 is a special case since it specifies a dictionary of
- * property IDs and property names.
- */
- if (id == 0) {
- throw new UnsupportedEncodingException("Dictionary not allowed here");
- }
-
- type = leis.readUInt();
-
- try {
- value = VariantSupport.read(leis, length, (int) type, codepage);
- } catch (UnsupportedVariantTypeException ex) {
- VariantSupport.writeUnsupportedTypeMessage(ex);
- value = ex.getValue();
- }
- }
-
-
- /**
- * Returns the property's ID.
- *
- * @return The ID value
- */
- public long getID() {
- return id;
- }
-
- /**
- * Sets the property's ID.
- *
- * @param id the ID
- */
- public void setID(final long id) {
- this.id = id;
- }
-
- /**
- * Returns the property's type.
- *
- * @return The type value
- */
- public long getType() {
- return type;
- }
-
- /**
- * Sets the property's type.
- *
- * @param type the property's type
- */
- public void setType(final long type) {
- this.type = type;
- }
-
- /**
- * Returns the property's value.
- *
- * @return The property's value
- */
- public Object getValue() {
- return value;
- }
-
- /**
- * Sets the property's value.
- *
- * @param value the property's value
- */
- public void setValue(final Object value) {
- this.value = value;
- }
-
-
-
-
-
-
-
-
- /**
- * Returns the property's size in bytes. This is always a multiple of 4.
- *
- * @param property The integer property to check
- *
- * @return the property's size in bytes
- *
- * @throws WritingNotSupportedException if HPSF does not yet support the
- * property's variant type.
- */
- protected int getSize(int property) throws WritingNotSupportedException
- {
- int length = Variant.getVariantLength(type);
- if (length >= 0 || type == Variant.VT_EMPTY) {
- /* Fixed length */
- return length;
- }
- if (length == -2) {
- /* Unknown length */
- throw new WritingNotSupportedException(type, null);
- }
-
- /* Variable length: */
- if (type == Variant.VT_LPSTR || type == Variant.VT_LPWSTR) {
- UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
- try {
- length = write(bos, property) - 2*LittleEndianConsts.INT_SIZE;
- /* Pad to multiples of 4. */
- length += (4 - (length & 0x3)) & 0x3;
- return length;
- } catch (IOException e) {
- throw new WritingNotSupportedException(type, this.value);
- }
- }
-
- throw new WritingNotSupportedException(type, this.value);
- }
-
-
-
- /**
- * Compares two properties.<p>
- *
- * Please beware that a property with
- * ID == 0 is a special case: It does not have a type, and its value is the
- * section's dictionary. Another special case are strings: Two properties
- * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;
- */
- @Override
- public boolean equals(final Object o) {
- if (!(o instanceof Property)) {
- return false;
- }
- final Property p = (Property) o;
- final Object pValue = p.getValue();
- final long pId = p.getID();
- if (id != pId || (id != 0 && !typesAreEqual(type, p.getType()))) {
- return false;
- }
- if (value == null && pValue == null) {
- return true;
- }
- if (value == null || pValue == null) {
- return false;
- }
-
- /* It's clear now that both values are non-null. */
- final Class<?> valueClass = value.getClass();
- final Class<?> pValueClass = pValue.getClass();
- if (!(valueClass.isAssignableFrom(pValueClass)) &&
- !(pValueClass.isAssignableFrom(valueClass))) {
- return false;
- }
-
- if (value instanceof byte[]) {
- // compare without padding bytes
- byte[] thisVal = (byte[]) value, otherVal = (byte[]) pValue;
- int len = unpaddedLength(thisVal);
- if (len != unpaddedLength(otherVal)) {
- return false;
- }
- for (int i=0; i<len; i++) {
- if (thisVal[i] != otherVal[i]) {
- return false;
- }
- }
- return true;
- }
-
- return value.equals(pValue);
- }
-
- /**
- * Byte arrays can be 0-padded up to 3 bytes to form a full quad array.
- * This returns the truncated length without the potentially 0-padded bytes
- *
- * @param buf the bytes
- * @return the truncated size with a maximum of 4 bytes shorter (3 bytes + trailing 0 of strings)
- */
- private static int unpaddedLength(byte[] buf) {
- final int end = (buf.length-(buf.length+3)%4);
- for (int i = buf.length; i>end; i--) {
- if (buf[i-1] != 0) {
- return i;
- }
- }
- return end;
- }
-
-
- private boolean typesAreEqual(final long t1, final long t2) {
- return (t1 == t2 ||
- (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
- (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR));
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id,type,value);
-
- }
-
- @Override
- public String toString() {
- return toString(Property.DEFAULT_CODEPAGE, null);
- }
-
- public String toString(int codepage, PropertyIDMap idMap) {
- final StringBuilder b = new StringBuilder();
- b.append("Property[");
- b.append("id: ");
- b.append(id);
- String idName = (idMap == null) ? null : idMap.get(id);
- if (idName == null) {
- idName = PropertyIDMap.getFallbackProperties().get(id);
- }
- if (idName != null) {
- b.append(" (");
- b.append(idName);
- b.append(")");
- }
- b.append(", type: ");
- b.append(getType());
- b.append(" (");
- b.append(getVariantName());
- b.append(") ");
- final Object value = getValue();
- b.append(", value: ");
- if (value instanceof String) {
- b.append((String)value);
- b.append("\n");
- UnsynchronizedByteArrayOutputStream bos = new UnsynchronizedByteArrayOutputStream();
- try {
- write(bos, codepage);
- } catch (Exception e) {
- LOG.atWarn().withThrowable(e).log("can't serialize string");
- }
-
- // skip length field
- if(bos.size() > 2*LittleEndianConsts.INT_SIZE) {
- final String hex = HexDump.dump(bos.toByteArray(), -2L*LittleEndianConsts.INT_SIZE, 2*LittleEndianConsts.INT_SIZE);
- b.append(hex);
- }
- } else if (value instanceof byte[]) {
- b.append("\n");
- byte[] bytes = (byte[])value;
- if(bytes.length > 0) {
- String hex = HexDump.dump(bytes, 0L, 0);
- b.append(hex);
- }
- } else if (value instanceof Date) {
- Date d = (Date)value;
- long filetime = Filetime.dateToFileTime(d);
- if (Filetime.isUndefined(d)) {
- b.append("<undefined>");
- } else if ((filetime >>> 32) == 0) {
- // if the upper dword isn't set, we deal with time intervals
- long l = filetime*100;
- TimeUnit tu = TimeUnit.NANOSECONDS;
- final long hr = tu.toHours(l);
- l -= TimeUnit.HOURS.toNanos(hr);
- final long min = tu.toMinutes(l);
- l -= TimeUnit.MINUTES.toNanos(min);
- final long sec = tu.toSeconds(l);
- l -= TimeUnit.SECONDS.toNanos(sec);
- final long ms = tu.toMillis(l);
-
- String str = String.format(Locale.ROOT, "%02d:%02d:%02d.%03d",hr,min,sec,ms);
- b.append(str);
- } else {
- // use ISO-8601 timestamp format
- DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
- df.setTimeZone(LocaleUtil.TIMEZONE_UTC);
- b.append(df.format(d));
- }
- } else if (type == Variant.VT_EMPTY || type == Variant.VT_NULL || value == null) {
- b.append("null");
- } else {
- b.append(value);
-
- String decoded = decodeValueFromID();
- if (decoded != null) {
- b.append(" (");
- b.append(decoded);
- b.append(")");
- }
- }
- b.append(']');
- return b.toString();
- }
-
- private String getVariantName() {
- if (getID() == 0) {
- return "dictionary";
- }
- return Variant.getVariantName(getType());
- }
-
- private String decodeValueFromID() {
- try {
- switch((int)getID()) {
- case PropertyIDMap.PID_CODEPAGE:
- return CodePageUtil.codepageToEncoding(((Number)value).intValue());
- case PropertyIDMap.PID_LOCALE:
- return LocaleUtil.getLocaleFromLCID(((Number)value).intValue());
- }
- } catch (Exception e) {
- LOG.atWarn().log("Can't decode id {}", box(getID()));
- }
- return null;
- }
-
- /**
- * Writes the property to an output stream.
- *
- * @param out The output stream to write to.
- * @param codepage The codepage to use for writing non-wide strings
- * @return the number of bytes written to the stream
- *
- * @throws IOException if an I/O error occurs
- * @throws WritingNotSupportedException if a variant type is to be
- * written that is not yet supported
- */
- public int write(final OutputStream out, final int codepage)
- throws IOException, WritingNotSupportedException {
- int length = 0;
- long variantType = getType();
-
- /* Ensure that wide strings are written if the codepage is Unicode. */
- // if (codepage == CodePageUtil.CP_UNICODE && variantType == Variant.VT_LPSTR) {
- // variantType = Variant.VT_LPWSTR;
- // }
-
- if (variantType == Variant.VT_LPSTR && codepage != CodePageUtil.CP_UTF16) {
- String csStr = CodePageUtil.codepageToEncoding(codepage > 0 ? codepage : Property.DEFAULT_CODEPAGE);
- if (!Charset.forName(csStr).newEncoder().canEncode((String)value)) {
- variantType = Variant.VT_LPWSTR;
- }
- }
-
- LittleEndian.putUInt(variantType, out);
- length += LittleEndianConsts.INT_SIZE;
- length += VariantSupport.write(out, variantType, getValue(), codepage);
- return length;
- }
-
- }
|