1    /*
2     *  ====================================================================
3     *  The Apache Software License, Version 1.1
4     *
5     *  Copyright (c) 2000 The Apache Software Foundation.  All rights
6     *  reserved.
7     *
8     *  Redistribution and use in source and binary forms, with or without
9     *  modification, are permitted provided that the following conditions
10    *  are met:
11    *
12    *  1. Redistributions of source code must retain the above copyright
13    *  notice, this list of conditions and the following disclaimer.
14    *
15    *  2. Redistributions in binary form must reproduce the above copyright
16    *  notice, this list of conditions and the following disclaimer in
17    *  the documentation and/or other materials provided with the
18    *  distribution.
19    *
20    *  3. The end-user documentation included with the redistribution,
21    *  if any, must include the following acknowledgment:
22    *  "This product includes software developed by the
23    *  Apache Software Foundation (http://www.apache.org/)."
24    *  Alternately, this acknowledgment may appear in the software itself,
25    *  if and wherever such third-party acknowledgments normally appear.
26    *
27    *  4. The names "Apache" and "Apache Software Foundation" must
28    *  not be used to endorse or promote products derived from this
29    *  software without prior written permission. For written
30    *  permission, please contact apache@apache.org.
31    *
32    *  5. Products derived from this software may not be called "Apache",
33    *  nor may "Apache" appear in their name, without prior written
34    *  permission of the Apache Software Foundation.
35    *
36    *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37    *  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38    *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39    *  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40    *  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41    *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42    *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43    *  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44    *  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45    *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46    *  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47    *  SUCH DAMAGE.
48    *  ====================================================================
49    *
50    *  This software consists of voluntary contributions made by many
51    *  individuals on behalf of the Apache Software Foundation.  For more
52    *  information on the Apache Software Foundation, please see
53    *  <http://www.apache.org/>.
54    */
55   package org.apache.poi.hpsf;
56   
57   import java.io.*;
58   import java.util.*;
59   import org.apache.poi.hpsf.wellknown.*;
60   import org.apache.poi.poifs.filesystem.*;
61   import org.apache.poi.util.LittleEndian;
62   
63   /**
64    * <p>Represents a property set in the Horrible Property Set Format
65    * (HPSF). These are usually metadata of a Microsoft Office
66    * document.</p>
67    *
68    * <p>An application that wants to access these metadata should create
69    * an instance of this class or one of its subclasses by calling the
70    * factory method {@link PropertySetFactory#create} and then retrieve
71    * the information its needs by calling appropriate methods.</p>
72    *
73    * <p>{@link PropertySetFactory#create} does its work by calling one
74    * of the constructors {@link PropertySet#PropertySet(InputStream)} or
75    * {@link PropertySet#PropertySet(byte[])}. If the constructor's
76    * argument is not in the Horrible Property Set Format, i.e. not a
77    * property set stream, or if any other error occurs, an appropriate
78    * exception is thrown.</p>
79    *
80    * <p>A {@link PropertySet} has a list of {@link Section}s, and each
81    * {@link Section} has a {@link Property} array. Use {@link
82    * #getSections} to retrieve the {@link Section}s, then call {@link
83    * Section#getProperties} for each {@link Section} to get hold of the
84    * {@link Property} arrays.</p> Since the vast majority of {@link
85    * PropertySet}s contains only a single {@link Section}, the
86    * convenience method {@link #getProperties} returns the properties of
87    * a {@link PropertySet}'s {@link Section} (throwing a {@link
88    * NoSingleSectionException} if the {@link PropertySet} contains more
89    * (or less) than exactly one {@link Section}).</p>
90    *
91    * @author Rainer Klute (klute@rainer-klute.de)
92    * @author Drew Varner (Drew.Varner hanginIn sc.edu)
93    * @version $Id: PropertySet.java,v 1.8 2002/07/30 14:56:02 klute Exp $
94    * @since 2002-02-09
95    */
96   public class PropertySet
97   {
98   
99       /**
100       * <p>The "byteOrder" field must equal this value.</p>
101       */
102      final static byte[] BYTE_ORDER_ASSERTION =
103  	new byte[]{(byte) 0xFE, (byte) 0xFF};
104  
105      /**
106       * <p>The "format" field must equal this value.</p>
107       */
108      final static byte[] FORMAT_ASSERTION =
109  	new byte[]{(byte) 0x00, (byte) 0x00};
110  
111      /**
112       * <p>Specifies this {@link PropertySet}'s byte order. See the
113       * HPFS documentation for details!</p>
114       */
115      private int byteOrder;
116  
117      /**
118       * <p>Returns the property set stream's low-level "byte order"
119       * field. It is always <tt>0xFFFE</tt> .</p>
120       *
121       * @return The property set stream's low-level "byte order" field.
122       */
123      public int getByteOrder()
124      {
125          return byteOrder;
126      }
127  
128  
129  
130      /**
131       * <p>Specifies this {@link PropertySet}'s format. See the HPFS
132       * documentation for details!</p>
133       */
134      private int format;
135  
136      /**
137       * <p>Returns the property set stream's low-level "format"
138       * field. It is always <tt>0x0000</tt> .</p>
139       *
140       * @return The property set stream's low-level "format" field.
141       */
142      public int getFormat()
143      {
144          return format;
145      }
146  
147  
148   
149      /**
150       * <p>Specifies the version of the operating system that created
151       * this {@link PropertySet}. See the HPFS documentation for
152       * details!</p>
153       */
154      private long osVersion;
155  
156      /**
157       * <p>Returns the property set stream's low-level "OS version"
158       * field.</p>
159       *
160       * @return The property set stream's low-level "OS version" field.
161       */
162      public long getOSVersion()
163      {
164          return osVersion;
165      }
166  
167  
168  
169      /**
170       * <p>Specifies this {@link PropertySet}'s "classID" field. See
171       * the HPFS documentation for details!</p>
172       */
173      private ClassID classID;
174  
175      /**
176       * <p>Returns the property set stream's low-level "class ID"
177       * field.</p>
178       *
179       * @return The property set stream's low-level "class ID" field.
180       */
181      public ClassID getClassID()
182      {
183          return classID;
184      }
185  
186  
187  
188      /**
189       * <p>The number of sections in this {@link PropertySet}.</p>
190       */
191      private long sectionCount;
192  
193  
194      /**
195       * <p>Returns the number of {@link Section}s in the property
196       * set.</p>
197       *
198       * @return The number of {@link Section}s in the property set.
199       */
200      public long getSectionCount()
201      {
202          return sectionCount;
203      }
204  
205  
206  
207      /**
208       * <p>The sections in this {@link PropertySet}.</p>
209       */
210      private List sections;
211  
212  
213      /**
214       * <p>Returns the {@link Section}s in the property set.</p>
215       *
216       * @return The {@link Section}s in the property set.
217       */
218      public List getSections()
219      {
220          return sections;
221      }
222  
223  
224  
225      /**
226       * <p>Creates an empty (uninitialized) {@link PropertySet}.</p>
227       *
228       * <p><strong>Please note:</strong> For the time being this
229       * constructor is protected since it is used for internal purposes
230       * only, but expect it to become public once the property set's
231       * writing functionality is implemented.</p>
232       */
233      protected PropertySet()
234      {}
235  
236  
237  
238      /**
239       * <p>Creates a {@link PropertySet} instance from an {@link
240       * InputStream} in the Horrible Property Set Format.</p>
241       *
242       * <p>The constructor reads the first few bytes from the stream
243       * and determines whether it is really a property set stream. If
244       * it is, it parses the rest of the stream. If it is not, it
245       * resets the stream to its beginning in order to let other
246       * components mess around with the data and throws an
247       * exception.</p>
248       *
249       * @param stream Holds the data making out the property set
250       * stream.
251       * @throws MarkUnsupportedException if the stream does not support
252       * the {@link InputStream#markSupported} method.
253       * @throws IOException if the {@link InputStream} cannot not be
254       * accessed as needed.
255       */
256      public PropertySet(final InputStream stream)
257  	throws NoPropertySetStreamException, MarkUnsupportedException,
258  	       IOException
259      {
260          if (isPropertySetStream(stream))
261  	{
262              final int avail = stream.available();
263              final byte[] buffer = new byte[avail];
264              stream.read(buffer, 0, buffer.length);
265              init(buffer, 0, buffer.length);
266          }
267  	else
268              throw new NoPropertySetStreamException();
269      }
270  
271  
272  
273      /**
274       * <p>Creates a {@link PropertySet} instance from a byte array
275       * that represents a stream in the Horrible Property Set
276       * Format.</p>
277       *
278       * @param stream The byte array holding the stream data.
279       * @param offset The offset in <var>stream</var> where the stream
280       * data begin. If the stream data begin with the first byte in the
281       * array, the <var>offset</var> is 0.
282       * @param length The length of the stream data.
283       * @throws NoPropertySetStreamException if the byte array is not a
284       * property set stream.
285       */
286      public PropertySet(final byte[] stream, final int offset, final int length)
287  	throws NoPropertySetStreamException
288      {
289          if (isPropertySetStream(stream, offset, length))
290              init(stream, offset, length);
291  	else
292              throw new NoPropertySetStreamException();
293      }
294  
295  
296  
297      /**
298       * <p>Creates a {@link PropertySet} instance from a byte array
299       * that represents a stream in the Horrible Property Set
300       * Format.</p>
301       *
302       * @param stream The byte array holding the stream data. The
303       * complete byte array contents is the stream data.
304       * @throws NoPropertySetStreamException if the byte array is not a
305       * property set stream.
306       */
307      public PropertySet(final byte[] stream) throws NoPropertySetStreamException
308      {
309          this(stream, 0, stream.length);
310      }
311  
312  
313  
314      /**
315       * <p>Checks whether an {@link InputStream} is in the Horrible
316       * Property Set Format.</p>
317       *
318       * @param stream The {@link InputStream} to check. In order to
319       * perform the check, the method reads the first bytes from the
320       * stream. After reading, the stream is reset to the position it
321       * had before reading. The {@link InputStream} must support the
322       * {@link InputStream#mark} method.
323       * @return <code>true</code> if the stream is a property set
324       * stream, else <code>false</code>.
325       * @throws MarkUnsupportedException if the {@link InputStream}
326       * does not support the {@link InputStream#mark} method.
327       */
328      public static boolean isPropertySetStream(final InputStream stream)
329  	throws MarkUnsupportedException, IOException
330      {
331          /*
332           * Read at most this many bytes.
333           */
334          final int BUFFER_SIZE = 50;
335  
336          /*
337           * Mark the current position in the stream so that we can
338           * reset to this position if the stream does not contain a
339           * property set.
340           */
341          if (!stream.markSupported())
342              throw new MarkUnsupportedException(stream.getClass().getName());
343  	stream.mark(BUFFER_SIZE);
344  
345          /*
346           * Read a couple of bytes from the stream.
347           */
348          final byte[] buffer = new byte[BUFFER_SIZE];
349          final int bytes =
350  	    stream.read(buffer, 0,
351  			Math.min(buffer.length, stream.available()));
352          final boolean isPropertySetStream =
353  	    isPropertySetStream(buffer, 0, bytes);
354          stream.reset();
355          return isPropertySetStream;
356      }
357  
358  
359  
360      /**
361       * <p>Checks whether a byte array is in the Horrible Property Set
362       * Format.</p>
363       *
364       * @param src The byte array to check.
365       * @param offset The offset in the byte array.
366       * @param length The significant number of bytes in the byte
367       * array. Only this number of bytes will be checked.
368       * @return <code>true</code> if the byte array is a property set
369       * stream, <code>false</code> if not.
370       */
371      public static boolean isPropertySetStream(final byte[] src, int offset,
372  					      final int length)
373      {
374          /*
375           * Read the header fields of the stream. They must always be
376           * there.
377           */
378          final int byteOrder = LittleEndian.getUShort(src, offset);
379          offset += LittleEndian.SHORT_SIZE;
380          byte[] temp = new byte[LittleEndian.SHORT_SIZE];
381          LittleEndian.putShort(temp,(short)byteOrder);
382          if (!Util.equal(temp, BYTE_ORDER_ASSERTION))
383              return false;
384  	final int format = LittleEndian.getUShort(src, offset);
385          offset += LittleEndian.SHORT_SIZE;
386          temp = new byte[LittleEndian.SHORT_SIZE];
387          LittleEndian.putShort(temp,(short)format);
388          if (!Util.equal(temp, FORMAT_ASSERTION))
389              return false;
390  	final long osVersion = LittleEndian.getUInt(src, offset);
391          offset += LittleEndian.INT_SIZE;
392          final ClassID classID = new ClassID(src, offset);
393          offset += ClassID.LENGTH;
394          final long sectionCount = LittleEndian.getUInt(src, offset);
395          offset += LittleEndian.INT_SIZE;
396          if (sectionCount < 1)
397              return false;
398  	return true;
399      }
400  
401  
402  
403      /**
404       * <p>Initializes this {@link PropertySet} instance from a byte
405       * array. The method assumes that it has been checked already that
406       * the byte array indeed represents a property set stream. It does
407       * no more checks on its own.</p>
408       *
409       * @param src Byte array containing the property set stream
410       * @param offset The property set stream starts at this offset
411       * from the beginning of <var>src</src>
412       * @param length Length of the property set stream.
413       */
414      private void init(final byte[] src, int offset, final int length)
415      {
416          /*
417           * Read the stream's header fields.
418           */
419          byteOrder = LittleEndian.getUShort(src, offset);
420          offset += LittleEndian.SHORT_SIZE;
421          format = LittleEndian.getUShort(src, offset);
422          offset += LittleEndian.SHORT_SIZE;
423          osVersion = LittleEndian.getUInt(src, offset);
424          offset += LittleEndian.INT_SIZE;
425          classID = new ClassID(src, offset);
426          offset += ClassID.LENGTH;
427          sectionCount = LittleEndian.getUInt(src, offset);
428          offset += LittleEndian.INT_SIZE;
429  
430          /*
431           * Read the sections, which are following the header. They
432           * start with an array of section descriptions. Each one
433           * consists of a format ID telling what the section contains
434           * and an offset telling how many bytes from the start of the
435           * stream the section begins.
436           */
437          /*
438           * Most property sets have only one section. The Document
439           * Summary Information stream has 2. Everything else is a rare
440           * exception and is no longer fostered by Microsoft.
441           */
442          sections = new ArrayList(2);
443  
444          /*
445           * Loop over the section descriptor array. Each descriptor
446           * consists of a ClassID and a DWord, and we have to increment
447           * "offset" accordingly.
448           */
449          for (int i = 0; i < sectionCount; i++)
450  	{
451              final Section s = new Section(src, offset);
452              offset += ClassID.LENGTH + LittleEndian.INT_SIZE;
453              sections.add(s);
454          }
455      }
456  
457  
458  
459      /**
460       * <p>Checks whether this {@link PropertySet} represents a Summary
461       * Information.</p>
462       *
463       * @return <code>true</code> if this {@link PropertySet}
464       * represents a Summary Information, else <code>false</code>.
465       */
466      public boolean isSummaryInformation()
467      {
468          return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
469  			  SectionIDMap.SUMMARY_INFORMATION_ID);
470      }
471  
472  
473  
474      /**
475       * <p>Checks whether this {@link PropertySet} is a Document
476       * Summary Information.</p>
477       *
478       * @return <code>true</code> if this {@link PropertySet}
479       * represents a Document Summary Information, else <code>false</code>.
480       */
481      public boolean isDocumentSummaryInformation()
482      {
483          return Util.equal(((Section) sections.get(0)).getFormatID().getBytes(),
484  			  SectionIDMap.DOCUMENT_SUMMARY_INFORMATION_ID);
485      }
486  
487  
488  
489      /**
490       * <p>Convenience method returning the {@link Property} array
491       * contained in this property set. It is a shortcut for getting
492       * the {@link PropertySet}'s {@link Section}s list and then
493       * getting the {@link Property} array from the first {@link
494       * Section}. However, it can only be used if the {@link
495       * PropertySet} contains exactly one {@link Section}, so check
496       * {@link #getSectionCount} first!</p>
497       *
498       * @return The properties of the only {@link Section} of this
499       * {@link PropertySet}.
500       * @throws NoSingleSectionException if the {@link PropertySet} has
501       * more or less than one {@link Section}.
502       */
503      public Property[] getProperties()
504  	throws NoSingleSectionException
505      {
506          return getSingleSection().getProperties();
507      }
508  
509  
510  
511      /**
512       * <p>Convenience method returning the value of the property with
513       * the specified ID. If the property is not available,
514       * <code>null</code> is returned and a subsequent call to {@link
515       * #wasNull} will return <code>true</code> .</p>
516       *
517       * @param id The property ID
518       * @return The property value
519       * @throws NoSingleSectionException if the {@link PropertySet} has
520       * more or less than one {@link Section}.
521       */
522      protected Object getProperty(final int id) throws NoSingleSectionException
523      {
524          return getSingleSection().getProperty(id);
525      }
526  
527  
528  
529      /**
530       * <p>Convenience method returning the value of a boolean property
531       * with the specified ID. If the property is not available,
532       * <code>false</code> is returned. A subsequent call to {@link
533       * #wasNull} will return <code>true</code> to let the caller
534       * distinguish that case from a real property value of
535       * <code>false</code>.</p>
536       *
537       * @param id The property ID
538       * @return The property value
539       * @throws NoSingleSectionException if the {@link PropertySet} has
540       * more or less than one {@link Section}.
541       */
542      protected boolean getPropertyBooleanValue(final int id)
543  	throws NoSingleSectionException
544      {
545          return getSingleSection().getPropertyBooleanValue(id);
546      }
547  
548  
549  
550      /**
551       * <p>Convenience method returning the value of the numeric
552       * property with the specified ID. If the property is not
553       * available, 0 is returned. A subsequent call to {@link #wasNull}
554       * will return <code>true</code> to let the caller distinguish
555       * that case from a real property value of 0.</p>
556       *
557       * @param id The property ID
558       * @return The propertyIntValue value
559       * @throws NoSingleSectionException if the {@link PropertySet} has
560       * more or less than one {@link Section}.
561       */
562      protected int getPropertyIntValue(final int id)
563  	throws NoSingleSectionException
564      {
565          return getSingleSection().getPropertyIntValue(id);
566      }
567  
568  
569  
570      /**
571       * <p>Checks whether the property which the last call to {@link
572       * #getPropertyIntValue} or {@link #getProperty} tried to access
573       * was available or not. This information might be important for
574       * callers of {@link #getPropertyIntValue} since the latter
575       * returns 0 if the property does not exist. Using {@link
576       * #wasNull}, the caller can distiguish this case from a
577       * property's real value of 0.</p>
578       *
579       * @return <code>true</code> if the last call to {@link
580       * #getPropertyIntValue} or {@link #getProperty} tried to access a
581       * property that was not available, else <code>false</code>.
582       * @throws NoSingleSectionException if the {@link PropertySet} has
583       * more than one {@link Section}.
584       */
585      public boolean wasNull() throws NoSingleSectionException
586      {
587          return getSingleSection().wasNull();
588      }
589  
590  
591  
592      /**
593       * <p>If the {@link PropertySet} has only a single section this
594       * method returns it.</p>
595       *
596       *@return The singleSection value
597       *@throws NoSingleSectionException if the {@link PropertySet} has
598       *more or less than exactly one {@link Section}.
599       */
600      public Section getSingleSection()
601      {
602          if (sectionCount != 1)
603  	    throw new NoSingleSectionException
604  		("Property set contains " + sectionCount + " sections.");
605  	return ((Section) sections.get(0));
606      }
607  
608  }
609