1
2 /* ====================================================================
3 Licensed to the Apache Software Foundation (ASF) under one or more
4 contributor license agreements. See the NOTICE file distributed with
5 this work for additional information regarding copyright ownership.
6 The ASF licenses this file to You under the Apache License, Version 2.0
7 (the "License"); you may not use this file except in compliance with
8 the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing, software
13 distributed under the License is distributed on an "AS IS" BASIS,
14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 See the License for the specific language governing permissions and
16 limitations under the License.
17 ==================================================================== */
18
19
20
21 package org.apache.poi.hslf.record;
22
23 import java.io.IOException;
24 import java.io.OutputStream;
25 import java.util.Vector;
26 import org.apache.poi.util.LittleEndian;
27 import org.apache.poi.util.POILogger;
28 import org.apache.poi.util.POILogFactory;
29 import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
30
31 /**
32 * This abstract class represents a record in the PowerPoint document.
33 * Record classes should extend with RecordContainer or RecordAtom, which
34 * extend this in turn.
35 *
36 * @author Nick Burch
37 */
38
39 public abstract class Record
40 {
41 // For logging
42 protected POILogger logger = POILogFactory.getLogger(this.getClass());
43
44 /**
45 * Is this record type an Atom record (only has data),
46 * or is it a non-Atom record (has other records)?
47 */
48 public abstract boolean isAnAtom();
49
50 /**
51 * Returns the type (held as a little endian in bytes 3 and 4)
52 * that this class handles
53 */
54 public abstract long getRecordType();
55
56 /**
57 * Fetch all the child records of this record
58 * If this record is an atom, will return null
59 * If this record is a non-atom, but has no children, will return
60 * an empty array
61 */
62 public abstract Record[] getChildRecords();
63
64 /**
65 * Have the contents printer out into an OutputStream, used when
66 * writing a file back out to disk
67 * (Normally, atom classes will keep their bytes around, but
68 * non atom classes will just request the bytes from their
69 * children, then chuck on their header and return)
70 */
71 public abstract void writeOut(OutputStream o) throws IOException;
72
73 /**
74 * When writing out, write out a signed int (32bit) in Little Endian format
75 */
76 public static void writeLittleEndian(int i,OutputStream o) throws IOException {
77 byte[] bi = new byte[4];
78 LittleEndian.putInt(bi,i);
79 o.write(bi);
80 }
81 /**
82 * When writing out, write out a signed short (16bit) in Little Endian format
83 */
84 public static void writeLittleEndian(short s,OutputStream o) throws IOException {
85 byte[] bs = new byte[2];
86 LittleEndian.putShort(bs,s);
87 o.write(bs);
88 }
89
90 /**
91 * Build and return the Record at the given offset.
92 * Note - does less error checking and handling than findChildRecords
93 * @param b The byte array to build from
94 * @param offset The offset to build at
95 */
96 public static Record buildRecordAtOffset(byte[] b, int offset) {
97 long type = LittleEndian.getUShort(b,offset+2);
98 long rlen = LittleEndian.getUInt(b,offset+4);
99
100 // Sanity check the length
101 int rleni = (int)rlen;
102 if(rleni < 0) { rleni = 0; }
103
104 return createRecordForType(type,b,offset,8+rleni);
105 }
106
107 /**
108 * Default method for finding child records of a container record
109 */
110 public static Record[] findChildRecords(byte[] b, int start, int len) {
111 Vector children = new Vector(5);
112
113 // Jump our little way along, creating records as we go
114 int pos = start;
115 while(pos <= (start+len-8)) {
116 long type = LittleEndian.getUShort(b,pos+2);
117 long rlen = LittleEndian.getUInt(b,pos+4);
118
119 // Sanity check the length
120 int rleni = (int)rlen;
121 if(rleni < 0) { rleni = 0; }
122
123 // Abort if first record is of type 0000 and length FFFF,
124 // as that's a sign of a screwed up record
125 if(pos == 0 && type == 0l && rleni == 0xffff) {
126 throw new CorruptPowerPointFileException("Corrupt document - starts with record of type 0000 and length 0xFFFF");
127 }
128
129 Record r = createRecordForType(type,b,pos,8+rleni);
130 if(r != null) {
131 children.add(r);
132 } else {
133 // Record was horribly corrupt
134 }
135 pos += 8;
136 pos += rleni;
137 }
138
139 // Turn the vector into an array, and return
140 Record[] cRecords = new Record[children.size()];
141 for(int i=0; i < children.size(); i++) {
142 cRecords[i] = (Record)children.get(i);
143 }
144 return cRecords;
145 }
146
147 /**
148 * For a given type (little endian bytes 3 and 4 in record header),
149 * byte array, start position and length:
150 * will return a Record object that will handle that record
151 *
152 * Remember that while PPT stores the record lengths as 8 bytes short
153 * (not including the size of the header), this code assumes you're
154 * passing in corrected lengths
155 */
156 public static Record createRecordForType(long type, byte[] b, int start, int len) {
157 Record toReturn = null;
158
159 // Handle case of a corrupt last record, whose claimed length
160 // would take us passed the end of the file
161 if(start + len > b.length) {
162 System.err.println("Warning: Skipping record of type " + type + " at position " + start + " which claims to be longer than the file! (" + len + " vs " + (b.length-start) + ")");
163 return null;
164 }
165
166 // We use the RecordTypes class to provide us with the right
167 // class to use for a given type
168 // A spot of reflection gets us the (byte[],int,int) constructor
169 // From there, we instanciate the class
170 // Any special record handling occurs once we have the class
171 Class c = null;
172 try {
173 c = RecordTypes.recordHandlingClass((int)type);
174 if(c == null) {
175 // How odd. RecordTypes normally subsitutes in
176 // a default handler class if it has heard of the record
177 // type but there's no support for it. Explicitly request
178 // that now
179 c = RecordTypes.recordHandlingClass( RecordTypes.Unknown.typeID );
180 }
181
182 // Grab the right constructor
183 java.lang.reflect.Constructor con = c.getDeclaredConstructor(new Class[] { byte[].class, Integer.TYPE, Integer.TYPE });
184 // Instantiate
185 toReturn = (Record)(con.newInstance(new Object[] { b, new Integer(start), new Integer(len) }));
186 } catch(InstantiationException ie) {
187 throw new RuntimeException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + ie, ie);
188 } catch(java.lang.reflect.InvocationTargetException ite) {
189 throw new RuntimeException("Couldn't instantiate the class for type with id " + type + " on class " + c + " : " + ite + "\nCause was : " + ite.getCause(), ite);
190 } catch(IllegalAccessException iae) {
191 throw new RuntimeException("Couldn't access the constructor for type with id " + type + " on class " + c + " : " + iae, iae);
192 } catch(NoSuchMethodException nsme) {
193 throw new RuntimeException("Couldn't access the constructor for type with id " + type + " on class " + c + " : " + nsme, nsme);
194 }
195
196 // Handling for special kinds of records follow
197
198 // If it's a position aware record, tell it where it is
199 if(toReturn instanceof PositionDependentRecord) {
200 PositionDependentRecord pdr = (PositionDependentRecord)toReturn;
201 pdr.setLastOnDiskOffset(start);
202 }
203
204 // Return the created record
205 return toReturn;
206 }
207 }