You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Property.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526
  1. /* ====================================================================
  2. Licensed to the Apache Software Foundation (ASF) under one or more
  3. contributor license agreements. See the NOTICE file distributed with
  4. this work for additional information regarding copyright ownership.
  5. The ASF licenses this file to You under the Apache License, Version 2.0
  6. (the "License"); you may not use this file except in compliance with
  7. the License. You may obtain a copy of the License at
  8. http://www.apache.org/licenses/LICENSE-2.0
  9. Unless required by applicable law or agreed to in writing, software
  10. distributed under the License is distributed on an "AS IS" BASIS,
  11. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. See the License for the specific language governing permissions and
  13. limitations under the License.
  14. ==================================================================== */
  15. package org.apache.poi.hpsf;
  16. import java.io.ByteArrayOutputStream;
  17. import java.io.IOException;
  18. import java.io.OutputStream;
  19. import java.io.UnsupportedEncodingException;
  20. import java.nio.charset.Charset;
  21. import java.text.DateFormat;
  22. import java.text.SimpleDateFormat;
  23. import java.util.Locale;
  24. import java.util.Objects;
  25. import java.util.concurrent.TimeUnit;
  26. import org.apache.poi.hpsf.wellknown.PropertyIDMap;
  27. import org.apache.poi.util.CodePageUtil;
  28. import org.apache.poi.util.HexDump;
  29. import org.apache.poi.util.LittleEndian;
  30. import org.apache.poi.util.LittleEndianByteArrayInputStream;
  31. import org.apache.poi.util.LittleEndianConsts;
  32. import org.apache.poi.util.LocaleUtil;
  33. import org.apache.poi.util.POILogFactory;
  34. import org.apache.poi.util.POILogger;
  35. /**
  36. * A property in a {@link Section} of a {@link PropertySet}.<p>
  37. *
  38. * The property's {@code ID} gives the property a meaning
  39. * in the context of its {@link Section}. Each {@link Section} spans
  40. * its own name space of property IDs.<p>
  41. *
  42. * The property's {@code type} determines how its
  43. * {@code value} is interpreted. For example, if the type is
  44. * {@link Variant#VT_LPSTR} (byte string), the value consists of a
  45. * DWord telling how many bytes the string contains. The bytes follow
  46. * immediately, including any null bytes that terminate the
  47. * string. The type {@link Variant#VT_I4} denotes a four-byte integer
  48. * value, {@link Variant#VT_FILETIME} some date and time (of a file).<p>
  49. *
  50. * Please note that not all {@link Variant} types yet. This might change
  51. * over time but largely depends on your feedback so that the POI team knows
  52. * which variant types are really needed. So please feel free to submit error
  53. * reports or patches for the types you need.
  54. *
  55. * @see Section
  56. * @see Variant
  57. * @see <a href="https://msdn.microsoft.com/en-us/library/dd942421.aspx">
  58. * [MS-OLEPS]: Object Linking and Embedding (OLE) Property Set Data Structures</a>
  59. */
  60. public class Property {
  61. /**
  62. * Default codepage for {@link CodePageString CodePageStrings}
  63. */
  64. public static final int DEFAULT_CODEPAGE = CodePageUtil.CP_WINDOWS_1252;
  65. private static final POILogger LOG = POILogFactory.getLogger(Property.class);
  66. /** The property's ID. */
  67. private long id;
  68. /** The property's type. */
  69. private long type;
  70. /** The property's value. */
  71. private Object value;
  72. /**
  73. * Creates an empty property. It must be filled using the set method to be usable.
  74. */
  75. public Property() {
  76. }
  77. /**
  78. * Creates a {@code Property} as a copy of an existing {@code Property}.
  79. *
  80. * @param p The property to copy.
  81. */
  82. public Property(Property p) {
  83. this(p.id, p.type, p.value);
  84. }
  85. /**
  86. * Creates a property.
  87. *
  88. * @param id the property's ID.
  89. * @param type the property's type, see {@link Variant}.
  90. * @param value the property's value. Only certain types are allowed, see
  91. * {@link Variant}.
  92. */
  93. public Property(final long id, final long type, final Object value) {
  94. this.id = id;
  95. this.type = type;
  96. this.value = value;
  97. }
  98. /**
  99. * Creates a {@link Property} instance by reading its bytes
  100. * from the property set stream.
  101. *
  102. * @param id The property's ID.
  103. * @param src The bytes the property set stream consists of.
  104. * @param offset The property's type/value pair's offset in the
  105. * section.
  106. * @param length The property's type/value pair's length in bytes.
  107. * @param codepage The section's and thus the property's
  108. * codepage. It is needed only when reading string values.
  109. * @exception UnsupportedEncodingException if the specified codepage is not
  110. * supported.
  111. */
  112. public Property(final long id, final byte[] src, final long offset, final int length, final int codepage)
  113. throws UnsupportedEncodingException {
  114. this.id = id;
  115. /*
  116. * ID 0 is a special case since it specifies a dictionary of
  117. * property IDs and property names.
  118. */
  119. if (id == 0) {
  120. throw new UnsupportedEncodingException("Dictionary not allowed here");
  121. }
  122. int o = (int) offset;
  123. type = LittleEndian.getUInt(src, o);
  124. o += LittleEndianConsts.INT_SIZE;
  125. try {
  126. value = VariantSupport.read(src, o, length, (int) type, codepage);
  127. } catch (UnsupportedVariantTypeException ex) {
  128. VariantSupport.writeUnsupportedTypeMessage(ex);
  129. value = ex.getValue();
  130. }
  131. }
  132. /**
  133. * Creates a {@link Property} instance by reading its bytes
  134. * from the property set stream.
  135. *
  136. * @param id The property's ID.
  137. * @param leis The bytes the property set stream consists of.
  138. * @param length The property's type/value pair's length in bytes.
  139. * @param codepage The section's and thus the property's
  140. * codepage. It is needed only when reading string values.
  141. * @exception UnsupportedEncodingException if the specified codepage is not
  142. * supported.
  143. */
  144. public Property(final long id, LittleEndianByteArrayInputStream leis, final int length, final int codepage)
  145. throws UnsupportedEncodingException {
  146. this.id = id;
  147. /*
  148. * ID 0 is a special case since it specifies a dictionary of
  149. * property IDs and property names.
  150. */
  151. if (id == 0) {
  152. throw new UnsupportedEncodingException("Dictionary not allowed here");
  153. }
  154. type = leis.readUInt();
  155. try {
  156. value = VariantSupport.read(leis, length, (int) type, codepage);
  157. } catch (UnsupportedVariantTypeException ex) {
  158. VariantSupport.writeUnsupportedTypeMessage(ex);
  159. value = ex.getValue();
  160. }
  161. }
  162. /**
  163. * Returns the property's ID.
  164. *
  165. * @return The ID value
  166. */
  167. public long getID() {
  168. return id;
  169. }
  170. /**
  171. * Sets the property's ID.
  172. *
  173. * @param id the ID
  174. */
  175. public void setID(final long id) {
  176. this.id = id;
  177. }
  178. /**
  179. * Returns the property's type.
  180. *
  181. * @return The type value
  182. */
  183. public long getType() {
  184. return type;
  185. }
  186. /**
  187. * Sets the property's type.
  188. *
  189. * @param type the property's type
  190. */
  191. public void setType(final long type) {
  192. this.type = type;
  193. }
  194. /**
  195. * Returns the property's value.
  196. *
  197. * @return The property's value
  198. */
  199. public Object getValue() {
  200. return value;
  201. }
  202. /**
  203. * Sets the property's value.
  204. *
  205. * @param value the property's value
  206. */
  207. public void setValue(final Object value) {
  208. this.value = value;
  209. }
  210. /**
  211. * Returns the property's size in bytes. This is always a multiple of 4.
  212. *
  213. * @param property The integer property to check
  214. *
  215. * @return the property's size in bytes
  216. *
  217. * @exception WritingNotSupportedException if HPSF does not yet support the
  218. * property's variant type.
  219. */
  220. protected int getSize(int property) throws WritingNotSupportedException
  221. {
  222. int length = Variant.getVariantLength(type);
  223. if (length >= 0 || type == Variant.VT_EMPTY) {
  224. /* Fixed length */
  225. return length;
  226. }
  227. if (length == -2) {
  228. /* Unknown length */
  229. throw new WritingNotSupportedException(type, null);
  230. }
  231. /* Variable length: */
  232. if (type == Variant.VT_LPSTR || type == Variant.VT_LPWSTR) {
  233. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  234. try {
  235. length = write(bos, property) - 2*LittleEndianConsts.INT_SIZE;
  236. /* Pad to multiples of 4. */
  237. length += (4 - (length & 0x3)) & 0x3;
  238. return length;
  239. } catch (IOException e) {
  240. throw new WritingNotSupportedException(type, this.value);
  241. }
  242. }
  243. throw new WritingNotSupportedException(type, this.value);
  244. }
  245. /**
  246. * Compares two properties.<p>
  247. *
  248. * Please beware that a property with
  249. * ID == 0 is a special case: It does not have a type, and its value is the
  250. * section's dictionary. Another special case are strings: Two properties
  251. * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;
  252. *
  253. * @see Object#equals(java.lang.Object)
  254. */
  255. @Override
  256. public boolean equals(final Object o) {
  257. if (!(o instanceof Property)) {
  258. return false;
  259. }
  260. final Property p = (Property) o;
  261. final Object pValue = p.getValue();
  262. final long pId = p.getID();
  263. if (id != pId || (id != 0 && !typesAreEqual(type, p.getType()))) {
  264. return false;
  265. }
  266. if (value == null && pValue == null) {
  267. return true;
  268. }
  269. if (value == null || pValue == null) {
  270. return false;
  271. }
  272. /* It's clear now that both values are non-null. */
  273. final Class<?> valueClass = value.getClass();
  274. final Class<?> pValueClass = pValue.getClass();
  275. if (!(valueClass.isAssignableFrom(pValueClass)) &&
  276. !(pValueClass.isAssignableFrom(valueClass))) {
  277. return false;
  278. }
  279. if (value instanceof byte[]) {
  280. // compare without padding bytes
  281. byte[] thisVal = (byte[]) value, otherVal = (byte[]) pValue;
  282. int len = unpaddedLength(thisVal);
  283. if (len != unpaddedLength(otherVal)) {
  284. return false;
  285. }
  286. for (int i=0; i<len; i++) {
  287. if (thisVal[i] != otherVal[i]) {
  288. return false;
  289. }
  290. }
  291. return true;
  292. }
  293. return value.equals(pValue);
  294. }
  295. /**
  296. * Byte arrays can be 0-padded up to 3 bytes to form a full quad array.
  297. * This returns the truncated length without the potentially 0-padded bytes
  298. *
  299. * @param buf the bytes
  300. * @return the truncated size with a maximum of 4 bytes shorter (3 bytes + trailing 0 of strings)
  301. */
  302. private static int unpaddedLength(byte[] buf) {
  303. final int end = (buf.length-(buf.length+3)%4);
  304. for (int i = buf.length; i>end; i--) {
  305. if (buf[i-1] != 0) {
  306. return i;
  307. }
  308. }
  309. return end;
  310. }
  311. private boolean typesAreEqual(final long t1, final long t2) {
  312. return (t1 == t2 ||
  313. (t1 == Variant.VT_LPSTR && t2 == Variant.VT_LPWSTR) ||
  314. (t2 == Variant.VT_LPSTR && t1 == Variant.VT_LPWSTR));
  315. }
  316. /**
  317. * @see Object#hashCode()
  318. */
  319. @Override
  320. public int hashCode() {
  321. return Objects.hash(id,type,value);
  322. }
  323. /**
  324. * @see Object#toString()
  325. */
  326. @Override
  327. public String toString() {
  328. return toString(Property.DEFAULT_CODEPAGE, null);
  329. }
  330. public String toString(int codepage, PropertyIDMap idMap) {
  331. final StringBuilder b = new StringBuilder();
  332. b.append("Property[");
  333. b.append("id: ");
  334. b.append(id);
  335. String idName = (idMap == null) ? null : idMap.get(id);
  336. if (idName == null) {
  337. idName = PropertyIDMap.getFallbackProperties().get(id);
  338. }
  339. if (idName != null) {
  340. b.append(" (");
  341. b.append(idName);
  342. b.append(")");
  343. }
  344. b.append(", type: ");
  345. b.append(getType());
  346. b.append(" (");
  347. b.append(getVariantName());
  348. b.append(") ");
  349. final Object value = getValue();
  350. b.append(", value: ");
  351. if (value instanceof String) {
  352. b.append((String)value);
  353. b.append("\n");
  354. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  355. try {
  356. write(bos, codepage);
  357. } catch (Exception e) {
  358. LOG.log(POILogger.WARN, "can't serialize string", e);
  359. }
  360. // skip length field
  361. if(bos.size() > 2*LittleEndianConsts.INT_SIZE) {
  362. final String hex = HexDump.dump(bos.toByteArray(), -2L*LittleEndianConsts.INT_SIZE, 2*LittleEndianConsts.INT_SIZE);
  363. b.append(hex);
  364. }
  365. } else if (value instanceof byte[]) {
  366. b.append("\n");
  367. byte[] bytes = (byte[])value;
  368. if(bytes.length > 0) {
  369. String hex = HexDump.dump(bytes, 0L, 0);
  370. b.append(hex);
  371. }
  372. } else if (value instanceof java.util.Date) {
  373. java.util.Date d = (java.util.Date)value;
  374. long filetime = Filetime.dateToFileTime(d);
  375. if (Filetime.isUndefined(d)) {
  376. b.append("<undefined>");
  377. } else if ((filetime >>> 32) == 0) {
  378. // if the upper dword isn't set, we deal with time intervals
  379. long l = filetime*100;
  380. TimeUnit tu = TimeUnit.NANOSECONDS;
  381. final long hr = tu.toHours(l);
  382. l -= TimeUnit.HOURS.toNanos(hr);
  383. final long min = tu.toMinutes(l);
  384. l -= TimeUnit.MINUTES.toNanos(min);
  385. final long sec = tu.toSeconds(l);
  386. l -= TimeUnit.SECONDS.toNanos(sec);
  387. final long ms = tu.toMillis(l);
  388. String str = String.format(Locale.ROOT, "%02d:%02d:%02d.%03d",hr,min,sec,ms);
  389. b.append(str);
  390. } else {
  391. // use ISO-8601 timestamp format
  392. DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT);
  393. df.setTimeZone(LocaleUtil.TIMEZONE_UTC);
  394. b.append(df.format(d));
  395. }
  396. } else if (type == Variant.VT_EMPTY || type == Variant.VT_NULL || value == null) {
  397. b.append("null");
  398. } else {
  399. b.append(value);
  400. String decoded = decodeValueFromID();
  401. if (decoded != null) {
  402. b.append(" (");
  403. b.append(decoded);
  404. b.append(")");
  405. }
  406. }
  407. b.append(']');
  408. return b.toString();
  409. }
  410. private String getVariantName() {
  411. if (getID() == 0) {
  412. return "dictionary";
  413. }
  414. return Variant.getVariantName(getType());
  415. }
  416. private String decodeValueFromID() {
  417. try {
  418. switch((int)getID()) {
  419. case PropertyIDMap.PID_CODEPAGE:
  420. return CodePageUtil.codepageToEncoding(((Number)value).intValue());
  421. case PropertyIDMap.PID_LOCALE:
  422. return LocaleUtil.getLocaleFromLCID(((Number)value).intValue());
  423. }
  424. } catch (Exception e) {
  425. LOG.log(POILogger.WARN, "Can't decode id "+getID());
  426. }
  427. return null;
  428. }
  429. /**
  430. * Writes the property to an output stream.
  431. *
  432. * @param out The output stream to write to.
  433. * @param codepage The codepage to use for writing non-wide strings
  434. * @return the number of bytes written to the stream
  435. *
  436. * @exception IOException if an I/O error occurs
  437. * @exception WritingNotSupportedException if a variant type is to be
  438. * written that is not yet supported
  439. */
  440. public int write(final OutputStream out, final int codepage)
  441. throws IOException, WritingNotSupportedException {
  442. int length = 0;
  443. long variantType = getType();
  444. /* Ensure that wide strings are written if the codepage is Unicode. */
  445. // if (codepage == CodePageUtil.CP_UNICODE && variantType == Variant.VT_LPSTR) {
  446. // variantType = Variant.VT_LPWSTR;
  447. // }
  448. if (variantType == Variant.VT_LPSTR && codepage != CodePageUtil.CP_UTF16) {
  449. String csStr = CodePageUtil.codepageToEncoding(codepage > 0 ? codepage : Property.DEFAULT_CODEPAGE);
  450. if (!Charset.forName(csStr).newEncoder().canEncode((String)value)) {
  451. variantType = Variant.VT_LPWSTR;
  452. }
  453. }
  454. LittleEndian.putUInt(variantType, out);
  455. length += LittleEndianConsts.INT_SIZE;
  456. length += VariantSupport.write(out, variantType, getValue(), codepage);
  457. return length;
  458. }
  459. }