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.

HSLFSlideShow.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789
  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;
  16. import java.io.ByteArrayInputStream;
  17. import java.io.ByteArrayOutputStream;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.io.InputStream;
  21. import java.io.OutputStream;
  22. import java.security.GeneralSecurityException;
  23. import java.util.ArrayList;
  24. import java.util.HashMap;
  25. import java.util.Hashtable;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.NavigableMap;
  29. import java.util.TreeMap;
  30. import org.apache.poi.POIDocument;
  31. import org.apache.poi.hpsf.PropertySet;
  32. import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
  33. import org.apache.poi.hslf.exceptions.HSLFException;
  34. import org.apache.poi.hslf.record.CurrentUserAtom;
  35. import org.apache.poi.hslf.record.DocumentEncryptionAtom;
  36. import org.apache.poi.hslf.record.ExOleObjStg;
  37. import org.apache.poi.hslf.record.PersistPtrHolder;
  38. import org.apache.poi.hslf.record.PersistRecord;
  39. import org.apache.poi.hslf.record.PositionDependentRecord;
  40. import org.apache.poi.hslf.record.Record;
  41. import org.apache.poi.hslf.record.RecordTypes;
  42. import org.apache.poi.hslf.record.UserEditAtom;
  43. import org.apache.poi.hslf.usermodel.ObjectData;
  44. import org.apache.poi.hslf.usermodel.PictureData;
  45. import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
  46. import org.apache.poi.poifs.filesystem.DirectoryNode;
  47. import org.apache.poi.poifs.filesystem.DocumentEntry;
  48. import org.apache.poi.poifs.filesystem.DocumentInputStream;
  49. import org.apache.poi.poifs.filesystem.EntryUtils;
  50. import org.apache.poi.poifs.filesystem.FilteringDirectoryNode;
  51. import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
  52. import org.apache.poi.poifs.filesystem.POIFSFileSystem;
  53. import org.apache.poi.util.LittleEndian;
  54. import org.apache.poi.util.POILogFactory;
  55. import org.apache.poi.util.POILogger;
  56. /**
  57. * This class contains the main functionality for the Powerpoint file
  58. * "reader". It is only a very basic class for now
  59. *
  60. * @author Nick Burch
  61. */
  62. public final class HSLFSlideShow extends POIDocument {
  63. public static final int UNSET_OFFSET = -1;
  64. // For logging
  65. private POILogger logger = POILogFactory.getLogger(this.getClass());
  66. // Holds metadata on where things are in our document
  67. private CurrentUserAtom currentUser;
  68. // Low level contents of the file
  69. private byte[] _docstream;
  70. // Low level contents
  71. private Record[] _records;
  72. // Raw Pictures contained in the pictures stream
  73. private List<PictureData> _pictures;
  74. // Embedded objects stored in storage records in the document stream, lazily populated.
  75. private ObjectData[] _objects;
  76. /**
  77. * Returns the directory in the underlying POIFSFileSystem for the
  78. * document that is open.
  79. */
  80. protected DirectoryNode getPOIFSDirectory() {
  81. return directory;
  82. }
  83. /**
  84. * Constructs a PowerPoint document from fileName. Parses the document
  85. * and places all the important stuff into data structures.
  86. *
  87. * @param fileName The name of the file to read.
  88. * @throws IOException if there is a problem while parsing the document.
  89. */
  90. public HSLFSlideShow(String fileName) throws IOException
  91. {
  92. this(new NPOIFSFileSystem(new File(fileName)));
  93. }
  94. /**
  95. * Constructs a Powerpoint document from an input stream. Parses the
  96. * document and places all the important stuff into data structures.
  97. *
  98. * @param inputStream the source of the data
  99. * @throws IOException if there is a problem while parsing the document.
  100. */
  101. public HSLFSlideShow(InputStream inputStream) throws IOException {
  102. //do Ole stuff
  103. this(new NPOIFSFileSystem(inputStream));
  104. }
  105. /**
  106. * Constructs a Powerpoint document from a POIFS Filesystem. Parses the
  107. * document and places all the important stuff into data structures.
  108. *
  109. * @param filesystem the POIFS FileSystem to read from
  110. * @throws IOException if there is a problem while parsing the document.
  111. */
  112. public HSLFSlideShow(POIFSFileSystem filesystem) throws IOException
  113. {
  114. this(filesystem.getRoot());
  115. }
  116. /**
  117. * Constructs a Powerpoint document from a POIFS Filesystem. Parses the
  118. * document and places all the important stuff into data structures.
  119. *
  120. * @param filesystem the POIFS FileSystem to read from
  121. * @throws IOException if there is a problem while parsing the document.
  122. */
  123. public HSLFSlideShow(NPOIFSFileSystem filesystem) throws IOException
  124. {
  125. this(filesystem.getRoot());
  126. }
  127. /**
  128. * Constructs a Powerpoint document from a specific point in a
  129. * POIFS Filesystem. Parses the document and places all the
  130. * important stuff into data structures.
  131. *
  132. * @param dir the POIFS directory to read from
  133. * @throws IOException if there is a problem while parsing the document.
  134. */
  135. public HSLFSlideShow(DirectoryNode dir) throws IOException {
  136. super(handleDualStorage(dir));
  137. // First up, grab the "Current User" stream
  138. // We need this before we can detect Encrypted Documents
  139. readCurrentUserStream();
  140. // Next up, grab the data that makes up the
  141. // PowerPoint stream
  142. readPowerPointStream();
  143. // Now, build records based on the PowerPoint stream
  144. buildRecords();
  145. // Look for any other streams
  146. readOtherStreams();
  147. }
  148. private static DirectoryNode handleDualStorage(DirectoryNode dir) throws IOException {
  149. // when there's a dual storage entry, use it, as the outer document can't be read quite probably ...
  150. String dualName = "PP97_DUALSTORAGE";
  151. if (!dir.hasEntry(dualName)) return dir;
  152. dir = (DirectoryNode)dir.getEntry(dualName);
  153. return dir;
  154. }
  155. /**
  156. * Constructs a new, empty, Powerpoint document.
  157. */
  158. public static final HSLFSlideShow create() {
  159. InputStream is = HSLFSlideShow.class.getResourceAsStream("data/empty.ppt");
  160. if (is == null) {
  161. throw new RuntimeException("Missing resource 'empty.ppt'");
  162. }
  163. try {
  164. return new HSLFSlideShow(is);
  165. } catch (IOException e) {
  166. throw new RuntimeException(e);
  167. }
  168. }
  169. /**
  170. * Extracts the main PowerPoint document stream from the
  171. * POI file, ready to be passed
  172. *
  173. * @throws IOException
  174. */
  175. private void readPowerPointStream() throws IOException
  176. {
  177. // Get the main document stream
  178. DocumentEntry docProps =
  179. (DocumentEntry)directory.getEntry("PowerPoint Document");
  180. // Grab the document stream
  181. _docstream = new byte[docProps.getSize()];
  182. directory.createDocumentInputStream("PowerPoint Document").read(_docstream);
  183. }
  184. /**
  185. * Builds the list of records, based on the contents
  186. * of the PowerPoint stream
  187. */
  188. private void buildRecords()
  189. {
  190. // The format of records in a powerpoint file are:
  191. // <little endian 2 byte "info">
  192. // <little endian 2 byte "type">
  193. // <little endian 4 byte "length">
  194. // If it has a zero length, following it will be another record
  195. // <xx xx yy yy 00 00 00 00> <xx xx yy yy zz zz zz zz>
  196. // If it has a length, depending on its type it may have children or data
  197. // If it has children, these will follow straight away
  198. // <xx xx yy yy zz zz zz zz <xx xx yy yy zz zz zz zz>>
  199. // If it has data, this will come straigh after, and run for the length
  200. // <xx xx yy yy zz zz zz zz dd dd dd dd dd dd dd>
  201. // All lengths given exclude the 8 byte record header
  202. // (Data records are known as Atoms)
  203. // Document should start with:
  204. // 0F 00 E8 03 ## ## ## ##
  205. // (type 1000 = document, info 00 0f is normal, rest is document length)
  206. // 01 00 E9 03 28 00 00 00
  207. // (type 1001 = document atom, info 00 01 normal, 28 bytes long)
  208. // 80 16 00 00 E0 10 00 00 xx xx xx xx xx xx xx xx
  209. // 05 00 00 00 0A 00 00 00 xx xx xx
  210. // (the contents of the document atom, not sure what it means yet)
  211. // (records then follow)
  212. // When parsing a document, look to see if you know about that type
  213. // of the current record. If you know it's a type that has children,
  214. // process the record's data area looking for more records
  215. // If you know about the type and it doesn't have children, either do
  216. // something with the data (eg TextRun) or skip over it
  217. // If you don't know about the type, play safe and skip over it (using
  218. // its length to know where the next record will start)
  219. //
  220. _records = read(_docstream, (int)currentUser.getCurrentEditOffset());
  221. }
  222. private Record[] read(byte[] docstream, int usrOffset){
  223. //sort found records by offset.
  224. //(it is not necessary but SlideShow.findMostRecentCoreRecords() expects them sorted)
  225. NavigableMap<Integer,Record> records = new TreeMap<Integer,Record>(); // offset -> record
  226. Map<Integer,Integer> persistIds = new HashMap<Integer,Integer>(); // offset -> persistId
  227. initRecordOffsets(docstream, usrOffset, records, persistIds);
  228. EncryptedSlideShow decryptData = new EncryptedSlideShow(docstream, records);
  229. for (Map.Entry<Integer,Record> entry : records.entrySet()) {
  230. Integer offset = entry.getKey();
  231. Record record = entry.getValue();
  232. Integer persistId = persistIds.get(offset);
  233. if (record == null) {
  234. // all plain records have been already added,
  235. // only new records need to be decrypted (tbd #35897)
  236. decryptData.decryptRecord(docstream, persistId, offset);
  237. record = Record.buildRecordAtOffset(docstream, offset);
  238. entry.setValue(record);
  239. }
  240. if (record instanceof PersistRecord) {
  241. ((PersistRecord)record).setPersistId(persistId);
  242. }
  243. }
  244. return records.values().toArray(new Record[records.size()]);
  245. }
  246. private void initRecordOffsets(byte[] docstream, int usrOffset, NavigableMap<Integer,Record> recordMap, Map<Integer,Integer> offset2id) {
  247. while (usrOffset != 0){
  248. UserEditAtom usr = (UserEditAtom) Record.buildRecordAtOffset(docstream, usrOffset);
  249. recordMap.put(usrOffset, usr);
  250. int psrOffset = usr.getPersistPointersOffset();
  251. PersistPtrHolder ptr = (PersistPtrHolder)Record.buildRecordAtOffset(docstream, psrOffset);
  252. recordMap.put(psrOffset, ptr);
  253. for(Map.Entry<Integer,Integer> entry : ptr.getSlideLocationsLookup().entrySet()) {
  254. Integer offset = entry.getValue();
  255. Integer id = entry.getKey();
  256. recordMap.put(offset, null); // reserve a slot for the record
  257. offset2id.put(offset, id);
  258. }
  259. usrOffset = usr.getLastUserEditAtomOffset();
  260. // check for corrupted user edit atom and try to repair it
  261. // if the next user edit atom offset is already known, we would go into an endless loop
  262. if (usrOffset > 0 && recordMap.containsKey(usrOffset)) {
  263. // a user edit atom is usually located 36 byte before the smallest known record offset
  264. usrOffset = recordMap.firstKey()-36;
  265. // check that we really are located on a user edit atom
  266. int ver_inst = LittleEndian.getUShort(docstream, usrOffset);
  267. int type = LittleEndian.getUShort(docstream, usrOffset+2);
  268. int len = LittleEndian.getInt(docstream, usrOffset+4);
  269. if (ver_inst == 0 && type == 4085 && (len == 0x1C || len == 0x20)) {
  270. logger.log(POILogger.WARN, "Repairing invalid user edit atom");
  271. usr.setLastUserEditAtomOffset(usrOffset);
  272. } else {
  273. throw new CorruptPowerPointFileException("Powerpoint document contains invalid user edit atom");
  274. }
  275. }
  276. }
  277. }
  278. public DocumentEncryptionAtom getDocumentEncryptionAtom() {
  279. for (Record r : _records) {
  280. if (r instanceof DocumentEncryptionAtom) {
  281. return (DocumentEncryptionAtom)r;
  282. }
  283. }
  284. return null;
  285. }
  286. /**
  287. * Find the "Current User" stream, and load it
  288. */
  289. private void readCurrentUserStream() {
  290. try {
  291. currentUser = new CurrentUserAtom(directory);
  292. } catch(IOException ie) {
  293. logger.log(POILogger.ERROR, "Error finding Current User Atom:\n" + ie);
  294. currentUser = new CurrentUserAtom();
  295. }
  296. }
  297. /**
  298. * Find any other streams from the filesystem, and load them
  299. */
  300. private void readOtherStreams() {
  301. // Currently, there aren't any
  302. }
  303. /**
  304. * Find and read in pictures contained in this presentation.
  305. * This is lazily called as and when we want to touch pictures.
  306. */
  307. @SuppressWarnings("unused")
  308. private void readPictures() throws IOException {
  309. _pictures = new ArrayList<PictureData>();
  310. // if the presentation doesn't contain pictures - will use a null set instead
  311. if (!directory.hasEntry("Pictures")) return;
  312. EncryptedSlideShow decryptData = new EncryptedSlideShow(getDocumentEncryptionAtom());
  313. DocumentEntry entry = (DocumentEntry)directory.getEntry("Pictures");
  314. byte[] pictstream = new byte[entry.getSize()];
  315. DocumentInputStream is = directory.createDocumentInputStream(entry);
  316. is.read(pictstream);
  317. is.close();
  318. int pos = 0;
  319. // An empty picture record (length 0) will take up 8 bytes
  320. while (pos <= (pictstream.length-8)) {
  321. int offset = pos;
  322. decryptData.decryptPicture(pictstream, offset);
  323. // Image signature
  324. int signature = LittleEndian.getUShort(pictstream, pos);
  325. pos += LittleEndian.SHORT_SIZE;
  326. // Image type + 0xF018
  327. int type = LittleEndian.getUShort(pictstream, pos);
  328. pos += LittleEndian.SHORT_SIZE;
  329. // Image size (excluding the 8 byte header)
  330. int imgsize = LittleEndian.getInt(pictstream, pos);
  331. pos += LittleEndian.INT_SIZE;
  332. // When parsing the BStoreDelay stream, [MS-ODRAW] says that we
  333. // should terminate if the type isn't 0xf007 or 0xf018->0xf117
  334. if (!((type == 0xf007) || (type >= 0xf018 && type <= 0xf117)))
  335. break;
  336. // The image size must be 0 or greater
  337. // (0 is allowed, but odd, since we do wind on by the header each
  338. // time, so we won't get stuck)
  339. if(imgsize < 0) {
  340. throw new CorruptPowerPointFileException("The file contains a picture, at position " + _pictures.size() + ", which has a negatively sized data length, so we can't trust any of the picture data");
  341. }
  342. // If they type (including the bonus 0xF018) is 0, skip it
  343. if(type == 0) {
  344. logger.log(POILogger.ERROR, "Problem reading picture: Invalid image type 0, on picture with length " + imgsize + ".\nYou document will probably become corrupted if you save it!");
  345. logger.log(POILogger.ERROR, "" + pos);
  346. } else {
  347. // Build the PictureData object from the data
  348. try {
  349. PictureData pict = PictureData.create(type - 0xF018);
  350. pict.setSignature(signature);
  351. // Copy the data, ready to pass to PictureData
  352. byte[] imgdata = new byte[imgsize];
  353. System.arraycopy(pictstream, pos, imgdata, 0, imgdata.length);
  354. pict.setRawData(imgdata);
  355. pict.setOffset(offset);
  356. _pictures.add(pict);
  357. } catch(IllegalArgumentException e) {
  358. logger.log(POILogger.ERROR, "Problem reading picture: " + e + "\nYou document will probably become corrupted if you save it!");
  359. }
  360. }
  361. pos += imgsize;
  362. }
  363. }
  364. /**
  365. * remove duplicated UserEditAtoms and merge PersistPtrHolder, i.e.
  366. * remove document edit history
  367. */
  368. public void normalizeRecords() {
  369. try {
  370. updateAndWriteDependantRecords(null, null);
  371. } catch (IOException e) {
  372. throw new CorruptPowerPointFileException(e);
  373. }
  374. _records = EncryptedSlideShow.normalizeRecords(_records);
  375. }
  376. /**
  377. * This is a helper functions, which is needed for adding new position dependent records
  378. * or finally write the slideshow to a file.
  379. *
  380. * @param os the stream to write to, if null only the references are updated
  381. * @param interestingRecords a map of interesting records (PersistPtrHolder and UserEditAtom)
  382. * referenced by their RecordType. Only the very last of each type will be saved to the map.
  383. * May be null, if not needed.
  384. * @throws IOException
  385. */
  386. public void updateAndWriteDependantRecords(OutputStream os, Map<RecordTypes.Type,PositionDependentRecord> interestingRecords)
  387. throws IOException {
  388. // For position dependent records, hold where they were and now are
  389. // As we go along, update, and hand over, to any Position Dependent
  390. // records we happen across
  391. Hashtable<Integer,Integer> oldToNewPositions = new Hashtable<Integer,Integer>();
  392. // First pass - figure out where all the position dependent
  393. // records are going to end up, in the new scheme
  394. // (Annoyingly, some powerpoint files have PersistPtrHolders
  395. // that reference slides after the PersistPtrHolder)
  396. UserEditAtom usr = null;
  397. PersistPtrHolder ptr = null;
  398. CountingOS cos = new CountingOS();
  399. for (Record record : _records) {
  400. // all top level records are position dependent
  401. assert(record instanceof PositionDependentRecord);
  402. PositionDependentRecord pdr = (PositionDependentRecord)record;
  403. int oldPos = pdr.getLastOnDiskOffset();
  404. int newPos = cos.size();
  405. pdr.setLastOnDiskOffset(newPos);
  406. if (oldPos != UNSET_OFFSET) {
  407. // new records don't need a mapping, as they aren't in a relation yet
  408. oldToNewPositions.put(oldPos,newPos);
  409. }
  410. // Grab interesting records as they come past
  411. // this will only save the very last record of each type
  412. RecordTypes.Type saveme = null;
  413. int recordType = (int)record.getRecordType();
  414. if (recordType == RecordTypes.PersistPtrIncrementalBlock.typeID) {
  415. saveme = RecordTypes.PersistPtrIncrementalBlock;
  416. ptr = (PersistPtrHolder)pdr;
  417. } else if (recordType == RecordTypes.UserEditAtom.typeID) {
  418. saveme = RecordTypes.UserEditAtom;
  419. usr = (UserEditAtom)pdr;
  420. }
  421. if (interestingRecords != null && saveme != null) {
  422. interestingRecords.put(saveme,pdr);
  423. }
  424. // Dummy write out, so the position winds on properly
  425. record.writeOut(cos);
  426. }
  427. assert(usr != null && ptr != null);
  428. Map<Integer,Integer> persistIds = new HashMap<Integer,Integer>();
  429. for (Map.Entry<Integer,Integer> entry : ptr.getSlideLocationsLookup().entrySet()) {
  430. persistIds.put(oldToNewPositions.get(entry.getValue()), entry.getKey());
  431. }
  432. EncryptedSlideShow encData = new EncryptedSlideShow(getDocumentEncryptionAtom());
  433. for (Record record : _records) {
  434. assert(record instanceof PositionDependentRecord);
  435. // We've already figured out their new location, and
  436. // told them that
  437. // Tell them of the positions of the other records though
  438. PositionDependentRecord pdr = (PositionDependentRecord)record;
  439. Integer persistId = persistIds.get(pdr.getLastOnDiskOffset());
  440. if (persistId == null) persistId = 0;
  441. // For now, we're only handling PositionDependentRecord's that
  442. // happen at the top level.
  443. // In future, we'll need the handle them everywhere, but that's
  444. // a bit trickier
  445. pdr.updateOtherRecordReferences(oldToNewPositions);
  446. // Whatever happens, write out that record tree
  447. if (os != null) {
  448. record.writeOut(encData.encryptRecord(os, persistId, record));
  449. }
  450. }
  451. // Update and write out the Current User atom
  452. int oldLastUserEditAtomPos = (int)currentUser.getCurrentEditOffset();
  453. Integer newLastUserEditAtomPos = oldToNewPositions.get(oldLastUserEditAtomPos);
  454. if(usr == null || newLastUserEditAtomPos == null || usr.getLastOnDiskOffset() != newLastUserEditAtomPos) {
  455. throw new HSLFException("Couldn't find the new location of the last UserEditAtom that used to be at " + oldLastUserEditAtomPos);
  456. }
  457. currentUser.setCurrentEditOffset(usr.getLastOnDiskOffset());
  458. }
  459. /**
  460. * Writes out the slideshow file the is represented by an instance
  461. * of this class.
  462. * It will write out the common OLE2 streams. If you require all
  463. * streams to be written out, pass in preserveNodes
  464. * @param out The OutputStream to write to.
  465. * @throws IOException If there is an unexpected IOException from
  466. * the passed in OutputStream
  467. */
  468. public void write(OutputStream out) throws IOException {
  469. // Write out, but only the common streams
  470. write(out,false);
  471. }
  472. /**
  473. * Writes out the slideshow file the is represented by an instance
  474. * of this class.
  475. * If you require all streams to be written out (eg Marcos, embeded
  476. * documents), then set preserveNodes to true
  477. * @param out The OutputStream to write to.
  478. * @param preserveNodes Should all OLE2 streams be written back out, or only the common ones?
  479. * @throws IOException If there is an unexpected IOException from
  480. * the passed in OutputStream
  481. */
  482. public void write(OutputStream out, boolean preserveNodes) throws IOException {
  483. // read properties and pictures, with old encryption settings where appropriate
  484. if(_pictures == null) {
  485. readPictures();
  486. }
  487. getDocumentSummaryInformation();
  488. // set new encryption settings
  489. EncryptedSlideShow encryptedSS = new EncryptedSlideShow(getDocumentEncryptionAtom());
  490. _records = encryptedSS.updateEncryptionRecord(_records);
  491. // Get a new Filesystem to write into
  492. NPOIFSFileSystem outFS = new NPOIFSFileSystem();
  493. // The list of entries we've written out
  494. List<String> writtenEntries = new ArrayList<String>(1);
  495. // Write out the Property Streams
  496. writeProperties(outFS, writtenEntries);
  497. BufAccessBAOS baos = new BufAccessBAOS();
  498. // For position dependent records, hold where they were and now are
  499. // As we go along, update, and hand over, to any Position Dependent
  500. // records we happen across
  501. updateAndWriteDependantRecords(baos, null);
  502. // Update our cached copy of the bytes that make up the PPT stream
  503. _docstream = new byte[baos.size()];
  504. System.arraycopy(baos.getBuf(), 0, _docstream, 0, baos.size());
  505. // Write the PPT stream into the POIFS layer
  506. ByteArrayInputStream bais = new ByteArrayInputStream(_docstream);
  507. outFS.createDocument(bais,"PowerPoint Document");
  508. writtenEntries.add("PowerPoint Document");
  509. currentUser.setEncrypted(encryptedSS.getDocumentEncryptionAtom() != null);
  510. currentUser.writeToFS(outFS);
  511. writtenEntries.add("Current User");
  512. if (_pictures.size() > 0) {
  513. BufAccessBAOS pict = new BufAccessBAOS();
  514. for (PictureData p : _pictures) {
  515. int offset = pict.size();
  516. p.write(pict);
  517. encryptedSS.encryptPicture(pict.getBuf(), offset);
  518. }
  519. outFS.createDocument(
  520. new ByteArrayInputStream(pict.getBuf(), 0, pict.size()), "Pictures"
  521. );
  522. writtenEntries.add("Pictures");
  523. }
  524. // If requested, write out any other streams we spot
  525. if(preserveNodes) {
  526. FilteringDirectoryNode sNode = new FilteringDirectoryNode(directory, writtenEntries);
  527. FilteringDirectoryNode dNode = new FilteringDirectoryNode(outFS.getRoot(), writtenEntries);
  528. EntryUtils.copyNodes(sNode, dNode);
  529. }
  530. // Send the POIFSFileSystem object out to the underlying stream
  531. outFS.writeFilesystem(out);
  532. }
  533. /**
  534. * For a given named property entry, either return it or null if
  535. * if it wasn't found
  536. *
  537. * @param setName The property to read
  538. * @return The value of the given property or null if it wasn't found.
  539. */
  540. protected PropertySet getPropertySet(String setName) {
  541. DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
  542. return (dea == null)
  543. ? super.getPropertySet(setName)
  544. : super.getPropertySet(setName, dea.getEncryptionInfo());
  545. }
  546. /**
  547. * Writes out the standard Documment Information Properties (HPSF)
  548. * @param outFS the NPOIFSFileSystem to write the properties into
  549. * @param writtenEntries a list of POIFS entries to add the property names too
  550. *
  551. * @throws IOException if an error when writing to the
  552. * {@link POIFSFileSystem} occurs
  553. */
  554. @Override
  555. protected void writeProperties(NPOIFSFileSystem outFS, List<String> writtenEntries) throws IOException {
  556. super.writeProperties(outFS, writtenEntries);
  557. DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
  558. if (dea != null) {
  559. CryptoAPIEncryptor enc = (CryptoAPIEncryptor)dea.getEncryptionInfo().getEncryptor();
  560. try {
  561. enc.getDataStream(outFS.getRoot()); // ignore OutputStream
  562. } catch (IOException e) {
  563. throw e;
  564. } catch (GeneralSecurityException e) {
  565. throw new IOException(e);
  566. }
  567. }
  568. }
  569. /* ******************* adding methods follow ********************* */
  570. /**
  571. * Adds a new root level record, at the end, but before the last
  572. * PersistPtrIncrementalBlock.
  573. */
  574. public synchronized int appendRootLevelRecord(Record newRecord) {
  575. int addedAt = -1;
  576. Record[] r = new Record[_records.length+1];
  577. boolean added = false;
  578. for(int i=(_records.length-1); i>=0; i--) {
  579. if(added) {
  580. // Just copy over
  581. r[i] = _records[i];
  582. } else {
  583. r[(i+1)] = _records[i];
  584. if(_records[i] instanceof PersistPtrHolder) {
  585. r[i] = newRecord;
  586. added = true;
  587. addedAt = i;
  588. }
  589. }
  590. }
  591. _records = r;
  592. return addedAt;
  593. }
  594. /**
  595. * Add a new picture to this presentation.
  596. *
  597. * @return offset of this picture in the Pictures stream
  598. */
  599. public int addPicture(PictureData img) {
  600. // Process any existing pictures if we haven't yet
  601. if(_pictures == null) {
  602. try {
  603. readPictures();
  604. } catch(IOException e) {
  605. throw new CorruptPowerPointFileException(e.getMessage());
  606. }
  607. }
  608. // Add the new picture in
  609. int offset = 0;
  610. if(_pictures.size() > 0) {
  611. PictureData prev = _pictures.get(_pictures.size() - 1);
  612. offset = prev.getOffset() + prev.getRawData().length + 8;
  613. }
  614. img.setOffset(offset);
  615. _pictures.add(img);
  616. return offset;
  617. }
  618. /* ******************* fetching methods follow ********************* */
  619. /**
  620. * Returns an array of all the records found in the slideshow
  621. */
  622. public Record[] getRecords() { return _records; }
  623. /**
  624. * Returns an array of the bytes of the file. Only correct after a
  625. * call to open or write - at all other times might be wrong!
  626. */
  627. public byte[] getUnderlyingBytes() { return _docstream; }
  628. /**
  629. * Fetch the Current User Atom of the document
  630. */
  631. public CurrentUserAtom getCurrentUserAtom() { return currentUser; }
  632. /**
  633. * Return array of pictures contained in this presentation
  634. *
  635. * @return array with the read pictures or <code>null</code> if the
  636. * presentation doesn't contain pictures.
  637. */
  638. public PictureData[] getPictures() {
  639. if(_pictures == null) {
  640. try {
  641. readPictures();
  642. } catch(IOException e) {
  643. throw new CorruptPowerPointFileException(e.getMessage());
  644. }
  645. }
  646. return _pictures.toArray(new PictureData[_pictures.size()]);
  647. }
  648. /**
  649. * Gets embedded object data from the slide show.
  650. *
  651. * @return the embedded objects.
  652. */
  653. public ObjectData[] getEmbeddedObjects() {
  654. if (_objects == null) {
  655. List<ObjectData> objects = new ArrayList<ObjectData>();
  656. for (Record r : _records) {
  657. if (r instanceof ExOleObjStg) {
  658. objects.add(new ObjectData((ExOleObjStg)r));
  659. }
  660. }
  661. _objects = objects.toArray(new ObjectData[objects.size()]);
  662. }
  663. return _objects;
  664. }
  665. private static class BufAccessBAOS extends ByteArrayOutputStream {
  666. public byte[] getBuf() {
  667. return buf;
  668. }
  669. }
  670. private static class CountingOS extends OutputStream {
  671. int count = 0;
  672. public void write(int b) throws IOException {
  673. count++;
  674. }
  675. public void write(byte[] b) throws IOException {
  676. count += b.length;
  677. }
  678. public void write(byte[] b, int off, int len) throws IOException {
  679. count += len;
  680. }
  681. public int size() {
  682. return count;
  683. }
  684. }
  685. }