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.

Record.java 6.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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.hslf.record;
  16. import java.io.IOException;
  17. import java.io.OutputStream;
  18. import java.util.ArrayList;
  19. import java.util.List;
  20. import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
  21. import org.apache.poi.hslf.exceptions.HSLFException;
  22. import org.apache.poi.hslf.record.RecordTypes.RecordConstructor;
  23. import org.apache.poi.util.LittleEndian;
  24. import org.apache.poi.util.POILogFactory;
  25. import org.apache.poi.util.POILogger;
  26. /**
  27. * This abstract class represents a record in the PowerPoint document.
  28. * Record classes should extend with RecordContainer or RecordAtom, which
  29. * extend this in turn.
  30. *
  31. * @author Nick Burch
  32. */
  33. public abstract class Record
  34. {
  35. // For logging
  36. protected static final POILogger logger = POILogFactory.getLogger(Record.class);
  37. /**
  38. * Is this record type an Atom record (only has data),
  39. * or is it a non-Atom record (has other records)?
  40. */
  41. public abstract boolean isAnAtom();
  42. /**
  43. * Returns the type (held as a little endian in bytes 3 and 4)
  44. * that this class handles
  45. */
  46. public abstract long getRecordType();
  47. /**
  48. * Fetch all the child records of this record
  49. * If this record is an atom, will return null
  50. * If this record is a non-atom, but has no children, will return
  51. * an empty array
  52. */
  53. public abstract Record[] getChildRecords();
  54. /**
  55. * Have the contents printer out into an OutputStream, used when
  56. * writing a file back out to disk
  57. * (Normally, atom classes will keep their bytes around, but
  58. * non atom classes will just request the bytes from their
  59. * children, then chuck on their header and return)
  60. */
  61. public abstract void writeOut(OutputStream o) throws IOException;
  62. /**
  63. * When writing out, write out a signed int (32bit) in Little Endian format
  64. */
  65. public static void writeLittleEndian(int i,OutputStream o) throws IOException {
  66. byte[] bi = new byte[4];
  67. LittleEndian.putInt(bi,0,i);
  68. o.write(bi);
  69. }
  70. /**
  71. * When writing out, write out a signed short (16bit) in Little Endian format
  72. */
  73. public static void writeLittleEndian(short s,OutputStream o) throws IOException {
  74. byte[] bs = new byte[2];
  75. LittleEndian.putShort(bs,0,s);
  76. o.write(bs);
  77. }
  78. /**
  79. * Build and return the Record at the given offset.
  80. * Note - does less error checking and handling than findChildRecords
  81. * @param b The byte array to build from
  82. * @param offset The offset to build at
  83. */
  84. public static Record buildRecordAtOffset(byte[] b, int offset) {
  85. long type = LittleEndian.getUShort(b,offset+2);
  86. long rlen = LittleEndian.getUInt(b,offset+4);
  87. // Sanity check the length
  88. int rleni = (int)rlen;
  89. if(rleni < 0) { rleni = 0; }
  90. return createRecordForType(type,b,offset,8+rleni);
  91. }
  92. /**
  93. * Default method for finding child records of a container record
  94. */
  95. public static Record[] findChildRecords(byte[] b, int start, int len) {
  96. List<Record> children = new ArrayList<>(5);
  97. // Jump our little way along, creating records as we go
  98. int pos = start;
  99. while(pos <= (start+len-8)) {
  100. long type = LittleEndian.getUShort(b,pos+2);
  101. long rlen = LittleEndian.getUInt(b,pos+4);
  102. // Sanity check the length
  103. int rleni = (int)rlen;
  104. if(rleni < 0) { rleni = 0; }
  105. // Abort if first record is of type 0000 and length FFFF,
  106. // as that's a sign of a screwed up record
  107. if(pos == 0 && type == 0L && rleni == 0xffff) {
  108. throw new CorruptPowerPointFileException("Corrupt document - starts with record of type 0000 and length 0xFFFF");
  109. }
  110. Record r = createRecordForType(type,b,pos,8+rleni);
  111. if(r != null) {
  112. children.add(r);
  113. }
  114. pos += 8;
  115. pos += rleni;
  116. }
  117. // Turn the vector into an array, and return
  118. return children.toArray( new Record[children.size()] );
  119. }
  120. /**
  121. * For a given type (little endian bytes 3 and 4 in record header),
  122. * byte array, start position and length:
  123. * will return a Record object that will handle that record
  124. *
  125. * Remember that while PPT stores the record lengths as 8 bytes short
  126. * (not including the size of the header), this code assumes you're
  127. * passing in corrected lengths
  128. */
  129. public static Record createRecordForType(long type, byte[] b, int start, int len) {
  130. // We use the RecordTypes class to provide us with the right
  131. // class to use for a given type
  132. // A spot of reflection gets us the (byte[],int,int) constructor
  133. // From there, we instanciate the class
  134. // Any special record handling occurs once we have the class
  135. RecordConstructor c = RecordTypes.forTypeID((short)type).recordConstructor;
  136. if (c == null) {
  137. // How odd. RecordTypes normally substitutes in
  138. // a default handler class if it has heard of the record
  139. // type but there's no support for it. Explicitly request
  140. // that now
  141. c = RecordTypes.UnknownRecordPlaceholder.recordConstructor;
  142. }
  143. final Record toReturn;
  144. try {
  145. toReturn = c.apply(b, start, len);
  146. } catch(RuntimeException e) {
  147. // Handle case of a corrupt last record, whose claimed length
  148. // would take us passed the end of the file
  149. if(start + len > b.length ) {
  150. logger.log(POILogger.WARN, "Warning: Skipping record of type " + type + " at position " + start + " which claims to be longer than the file! (" + len + " vs " + (b.length-start) + ")");
  151. return null;
  152. }
  153. throw new HSLFException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + e, e);
  154. }
  155. // Handling for special kinds of records follow
  156. // If it's a position aware record, tell it where it is
  157. if(toReturn instanceof PositionDependentRecord) {
  158. PositionDependentRecord pdr = (PositionDependentRecord)toReturn;
  159. pdr.setLastOnDiskOffset(start);
  160. }
  161. // Return the created record
  162. return toReturn;
  163. }
  164. }