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.

POIFSFileSystem.java 36KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007
  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.poifs.filesystem;
  16. import java.io.Closeable;
  17. import java.io.File;
  18. import java.io.FileInputStream;
  19. import java.io.FileOutputStream;
  20. import java.io.IOException;
  21. import java.io.InputStream;
  22. import java.io.OutputStream;
  23. import java.nio.ByteBuffer;
  24. import java.nio.channels.Channels;
  25. import java.nio.channels.FileChannel;
  26. import java.nio.channels.ReadableByteChannel;
  27. import java.util.ArrayList;
  28. import java.util.Collections;
  29. import java.util.Iterator;
  30. import java.util.List;
  31. import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
  32. import org.apache.logging.log4j.LogManager;
  33. import org.apache.logging.log4j.Logger;
  34. import org.apache.poi.EmptyFileException;
  35. import org.apache.poi.poifs.common.POIFSBigBlockSize;
  36. import org.apache.poi.poifs.common.POIFSConstants;
  37. import org.apache.poi.poifs.dev.POIFSViewable;
  38. import org.apache.poi.poifs.nio.ByteArrayBackedDataSource;
  39. import org.apache.poi.poifs.nio.DataSource;
  40. import org.apache.poi.poifs.nio.FileBackedDataSource;
  41. import org.apache.poi.poifs.property.DirectoryProperty;
  42. import org.apache.poi.poifs.property.DocumentProperty;
  43. import org.apache.poi.poifs.property.PropertyTable;
  44. import org.apache.poi.poifs.storage.BATBlock;
  45. import org.apache.poi.poifs.storage.BATBlock.BATBlockAndIndex;
  46. import org.apache.poi.poifs.storage.HeaderBlock;
  47. import org.apache.poi.util.IOUtils;
  48. import org.apache.poi.util.Internal;
  49. /**
  50. * <p>This is the main class of the POIFS system; it manages the entire
  51. * life cycle of the filesystem.</p>
  52. * <p>This is the new NIO version, which uses less memory</p>
  53. */
  54. public class POIFSFileSystem extends BlockStore
  55. implements POIFSViewable, Closeable {
  56. //arbitrarily selected; may need to increase
  57. private static final int DEFAULT_MAX_RECORD_LENGTH = 100_000;
  58. private static int MAX_RECORD_LENGTH = DEFAULT_MAX_RECORD_LENGTH;
  59. private static final int MAX_ALLOCATION_SIZE = 250_000_000;
  60. private static final Logger LOG = LogManager.getLogger(POIFSFileSystem.class);
  61. /**
  62. * Maximum number size (in blocks) of the allocation table as supported by
  63. * POI.
  64. * <p>
  65. * This constant has been chosen to help POI identify corrupted data in the
  66. * header block (rather than crash immediately with {@link OutOfMemoryError}
  67. * ). It's not clear if the compound document format actually specifies any
  68. * upper limits. For files with 512 byte blocks, having an allocation table
  69. * of 65,335 blocks would correspond to a total file size of 4GB. Needless
  70. * to say, POI probably cannot handle files anywhere near that size.
  71. */
  72. private static final int MAX_BLOCK_COUNT = 65535;
  73. private POIFSMiniStore _mini_store;
  74. private PropertyTable _property_table;
  75. private final List<BATBlock> _xbat_blocks;
  76. private final List<BATBlock> _bat_blocks;
  77. private HeaderBlock _header;
  78. private DirectoryNode _root;
  79. protected DataSource _data;
  80. /**
  81. * What big block size the file uses. Most files
  82. * use 512 bytes, but a few use 4096
  83. */
  84. private POIFSBigBlockSize bigBlockSize =
  85. POIFSConstants.SMALLER_BIG_BLOCK_SIZE_DETAILS;
  86. /**
  87. * @param length the max record length allowed for POIFSFileSystem
  88. */
  89. public static void setMaxRecordLength(int length) {
  90. MAX_RECORD_LENGTH = length;
  91. }
  92. /**
  93. * @return the max record length allowed for POIFSFileSystem
  94. */
  95. public static int getMaxRecordLength() {
  96. return MAX_RECORD_LENGTH;
  97. }
  98. private POIFSFileSystem(boolean newFS) {
  99. _header = new HeaderBlock(bigBlockSize);
  100. _property_table = new PropertyTable(_header);
  101. _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), new ArrayList<>(), _header);
  102. _xbat_blocks = new ArrayList<>();
  103. _bat_blocks = new ArrayList<>();
  104. _root = null;
  105. if (newFS) {
  106. createNewDataSource();
  107. }
  108. }
  109. protected void createNewDataSource() {
  110. // Data needs to initially hold just the header block,
  111. // a single bat block, and an empty properties section
  112. long blockSize = Math.multiplyExact(bigBlockSize.getBigBlockSize(), 3L);
  113. _data = new ByteArrayBackedDataSource(IOUtils.safelyAllocate(blockSize, MAX_RECORD_LENGTH));
  114. }
  115. /**
  116. * Constructor, intended for writing
  117. */
  118. public POIFSFileSystem() {
  119. this(true);
  120. // Reserve block 0 for the start of the Properties Table
  121. // Create a single empty BAT, at pop that at offset 1
  122. _header.setBATCount(1);
  123. _header.setBATArray(new int[]{1});
  124. BATBlock bb = BATBlock.createEmptyBATBlock(bigBlockSize, false);
  125. bb.setOurBlockIndex(1);
  126. _bat_blocks.add(bb);
  127. setNextBlock(0, POIFSConstants.END_OF_CHAIN);
  128. setNextBlock(1, POIFSConstants.FAT_SECTOR_BLOCK);
  129. _property_table.setStartBlock(0);
  130. }
  131. /**
  132. * <p>Creates a POIFSFileSystem from a {@code File}. This uses less memory than
  133. * creating from an {@code InputStream}. The File will be opened read-only</p>
  134. *
  135. * <p>Note that with this constructor, you will need to call {@link #close()}
  136. * when you're done to have the underlying file closed, as the file is
  137. * kept open during normal operation to read the data out.</p>
  138. *
  139. * @param file the File from which to read the data
  140. * @throws IOException on errors reading, or on invalid data
  141. */
  142. public POIFSFileSystem(File file)
  143. throws IOException {
  144. this(file, true);
  145. }
  146. /**
  147. * <p>Creates a POIFSFileSystem from a {@code File}. This uses less memory than
  148. * creating from an {@code InputStream}.</p>
  149. *
  150. * <p>Note that with this constructor, you will need to call {@link #close()}
  151. * when you're done to have the underlying file closed, as the file is
  152. * kept open during normal operation to read the data out.</p>
  153. *
  154. * @param file the File from which to read or read/write the data
  155. * @param readOnly whether the POIFileSystem will only be used in read-only mode
  156. * @throws IOException on errors reading, or on invalid data
  157. */
  158. public POIFSFileSystem(File file, boolean readOnly)
  159. throws IOException {
  160. this(null, file, readOnly, true, true);
  161. }
  162. /**
  163. * <p>Creates a POIFSFileSystem from an open {@code FileChannel}. This uses
  164. * less memory than creating from an {@code InputStream}. The stream will
  165. * be used in read-only mode.</p>
  166. *
  167. * <p>Note that with this constructor, you will need to call {@link #close()}
  168. * when you're done to have the underlying Channel closed, as the channel is
  169. * kept open during normal operation to read the data out. For legacy reasons,
  170. * the channel is not closed if there is an error creating the POIFSFileSystem.</p>
  171. *
  172. * @param channel the FileChannel from which to read the data
  173. * @throws IOException on errors reading, or on invalid data
  174. * @see POIFSFileSystem(FileChannel, boolean, boolean) this constructor gives more control over whether to
  175. * close the provided channel
  176. */
  177. public POIFSFileSystem(FileChannel channel)
  178. throws IOException {
  179. this(channel, true);
  180. }
  181. /**
  182. * <p>Creates a POIFSFileSystem from an open {@code FileChannel}. This uses
  183. * less memory than creating from an {@code InputStream}.</p>
  184. *
  185. * <p>Note that with this constructor, you will need to call {@link #close()}
  186. * when you're done to have the underlying Channel closed, as the channel is
  187. * kept open during normal operation to read the data out. For legacy reasons,
  188. * the channel is not closed if there is an error creating the POIFSFileSystem.</p>
  189. *
  190. * @param channel the FileChannel from which to read or read/write the data
  191. * @param readOnly whether the POIFileSystem will only be used in read-only mode
  192. * @throws IOException on errors reading, or on invalid data
  193. * @see POIFSFileSystem(FileChannel, boolean, boolean) this constructor gives more control over whether to
  194. * close the provided channel
  195. */
  196. public POIFSFileSystem(FileChannel channel, boolean readOnly)
  197. throws IOException {
  198. this(channel, null, readOnly, false, true);
  199. }
  200. /**
  201. * <p>Creates a POIFSFileSystem from an open {@code FileChannel}. This uses
  202. * less memory than creating from an {@code InputStream}.</p>
  203. *
  204. * <p>Note that with this constructor, you will need to call {@link #close()}
  205. * when you're done to have the underlying resources closed. The <code>closeChannel</code>
  206. * parameter controls whether the provided channel is closed.</p>
  207. *
  208. * @param channel the FileChannel from which to read or read/write the data
  209. * @param readOnly whether the POIFileSystem will only be used in read-only mode
  210. * @param closeChannel whether the provided FileChannel should be closed when
  211. * {@link #close()} is called, or when this constructor throws
  212. * an exception
  213. * @throws IOException on errors reading, or on invalid data
  214. * @since POI 5.1.0
  215. */
  216. public POIFSFileSystem(FileChannel channel, boolean readOnly, boolean closeChannel)
  217. throws IOException {
  218. this(channel, null, readOnly, closeChannel, closeChannel);
  219. }
  220. @SuppressWarnings("java:S2095")
  221. private POIFSFileSystem(FileChannel channel, File srcFile, boolean readOnly, boolean closeChannelOnError,
  222. boolean closeChannelOnClose) throws IOException {
  223. this(false);
  224. try {
  225. // Initialize the datasource
  226. if (srcFile != null) {
  227. if (srcFile.length() == 0)
  228. throw new EmptyFileException(srcFile);
  229. FileBackedDataSource d = new FileBackedDataSource(srcFile, readOnly);
  230. channel = d.getChannel();
  231. _data = d;
  232. } else {
  233. _data = new FileBackedDataSource(channel, readOnly, closeChannelOnClose);
  234. }
  235. // Get the header
  236. ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
  237. IOUtils.readFully(channel, headerBuffer);
  238. // Have the header processed
  239. _header = new HeaderBlock(headerBuffer);
  240. // Now process the various entries
  241. readCoreContents();
  242. } catch (IOException | RuntimeException e) {
  243. // Comes from Iterators etc.
  244. // TODO Decide if we can handle these better whilst
  245. // still sticking to the iterator contract
  246. if (closeChannelOnError && channel != null) {
  247. channel.close();
  248. }
  249. throw e;
  250. }
  251. }
  252. /**
  253. * Create a POIFSFileSystem from an {@code InputStream}. Normally the stream is read until
  254. * EOF. The stream is always closed.
  255. * <p>
  256. * Some streams are usable after reaching EOF (typically those that return {@code true}
  257. * for {@code markSupported()}). In the unlikely case that the caller has such a stream
  258. * <i>and</i> needs to use it after this constructor completes, a work around is to wrap the
  259. * stream in order to trap the {@code close()} call. A convenience method (
  260. * {@code createNonClosingInputStream()}) has been provided for this purpose:
  261. * <pre>
  262. * InputStream wrappedStream = POIFSFileSystem.createNonClosingInputStream(is);
  263. * HSSFWorkbook wb = new HSSFWorkbook(wrappedStream);
  264. * is.reset();
  265. * doSomethingElse(is);
  266. * </pre>
  267. * Note also the special case of {@code ByteArrayInputStream} for which the {@code close()}
  268. * method does nothing.
  269. * <pre>
  270. * ByteArrayInputStream bais = ...
  271. * HSSFWorkbook wb = new HSSFWorkbook(bais); // calls bais.close() !
  272. * bais.reset(); // no problem
  273. * doSomethingElse(bais);
  274. * </pre>
  275. *
  276. * @param stream the InputStream from which to read the data
  277. * @throws IOException on errors reading, or on invalid data
  278. */
  279. public POIFSFileSystem(InputStream stream)
  280. throws IOException {
  281. this(false);
  282. boolean success = false;
  283. try (ReadableByteChannel channel = Channels.newChannel(stream)) {
  284. // Turn our InputStream into something NIO based
  285. // Get the header
  286. ByteBuffer headerBuffer = ByteBuffer.allocate(POIFSConstants.SMALLER_BIG_BLOCK_SIZE);
  287. IOUtils.readFully(channel, headerBuffer);
  288. // Have the header processed
  289. _header = new HeaderBlock(headerBuffer);
  290. // Sanity check the block count
  291. sanityCheckBlockCount(_header.getBATCount());
  292. // We need to buffer the whole file into memory when
  293. // working with an InputStream.
  294. // The max possible size is when each BAT block entry is used
  295. long maxSize = BATBlock.calculateMaximumSize(_header);
  296. if (maxSize > Integer.MAX_VALUE) {
  297. throw new IllegalArgumentException("Unable read a >2gb file via an InputStream");
  298. }
  299. // don't allow huge allocations with invalid header-values
  300. IOUtils.safelyAllocateCheck(maxSize, MAX_ALLOCATION_SIZE);
  301. ByteBuffer data = ByteBuffer.allocate((int) maxSize);
  302. // Copy in the header
  303. headerBuffer.position(0);
  304. data.put(headerBuffer);
  305. data.position(headerBuffer.capacity());
  306. // Now read the rest of the stream
  307. IOUtils.readFully(channel, data);
  308. success = true;
  309. // Turn it into a DataSource
  310. _data = new ByteArrayBackedDataSource(data.array(), data.position());
  311. } finally {
  312. // As per the constructor contract, always close the stream
  313. closeInputStream(stream, success);
  314. }
  315. // Now process the various entries
  316. readCoreContents();
  317. }
  318. /**
  319. * @param stream the stream to be closed
  320. * @param success {@code false} if an exception is currently being thrown in the calling method
  321. */
  322. private void closeInputStream(InputStream stream, boolean success) {
  323. try {
  324. stream.close();
  325. } catch (IOException e) {
  326. if (success) {
  327. throw new RuntimeException(e);
  328. }
  329. // else not success? Try block did not complete normally
  330. // just print stack trace and leave original ex to be thrown
  331. LOG.atError().withThrowable(e).log("can't close input stream");
  332. }
  333. }
  334. /**
  335. * Read and process the PropertiesTable and the
  336. * FAT / XFAT blocks, so that we're ready to
  337. * work with the file
  338. */
  339. private void readCoreContents() throws IOException {
  340. // Grab the block size
  341. bigBlockSize = _header.getBigBlockSize();
  342. // Each block should only ever be used by one of the
  343. // FAT, XFAT or Property Table. Ensure it does
  344. ChainLoopDetector loopDetector = getChainLoopDetector();
  345. // Read the FAT blocks
  346. for (int fatAt : _header.getBATArray()) {
  347. readBAT(fatAt, loopDetector);
  348. }
  349. // Work out how many FAT blocks remain in the XFATs
  350. int remainingFATs = _header.getBATCount() - _header.getBATArray().length;
  351. // Now read the XFAT blocks, and the FATs within them
  352. BATBlock xfat;
  353. int nextAt = _header.getXBATIndex();
  354. for (int i = 0; i < _header.getXBATCount(); i++) {
  355. loopDetector.claim(nextAt);
  356. ByteBuffer fatData = getBlockAt(nextAt);
  357. xfat = BATBlock.createBATBlock(bigBlockSize, fatData);
  358. xfat.setOurBlockIndex(nextAt);
  359. nextAt = xfat.getValueAt(bigBlockSize.getXBATEntriesPerBlock());
  360. _xbat_blocks.add(xfat);
  361. // Process all the (used) FATs from this XFAT
  362. int xbatFATs = Math.min(remainingFATs, bigBlockSize.getXBATEntriesPerBlock());
  363. for (int j = 0; j < xbatFATs; j++) {
  364. int fatAt = xfat.getValueAt(j);
  365. if (fatAt == POIFSConstants.UNUSED_BLOCK || fatAt == POIFSConstants.END_OF_CHAIN) break;
  366. readBAT(fatAt, loopDetector);
  367. }
  368. remainingFATs -= xbatFATs;
  369. }
  370. // We're now able to load steams
  371. // Use this to read in the properties
  372. _property_table = new PropertyTable(_header, this);
  373. // Finally read the Small Stream FAT (SBAT) blocks
  374. BATBlock sfat;
  375. List<BATBlock> sbats = new ArrayList<>();
  376. _mini_store = new POIFSMiniStore(this, _property_table.getRoot(), sbats, _header);
  377. nextAt = _header.getSBATStart();
  378. for (int i = 0; i < _header.getSBATCount() && nextAt != POIFSConstants.END_OF_CHAIN; i++) {
  379. loopDetector.claim(nextAt);
  380. ByteBuffer fatData = getBlockAt(nextAt);
  381. sfat = BATBlock.createBATBlock(bigBlockSize, fatData);
  382. sfat.setOurBlockIndex(nextAt);
  383. sbats.add(sfat);
  384. nextAt = getNextBlock(nextAt);
  385. }
  386. }
  387. private void readBAT(int batAt, ChainLoopDetector loopDetector) throws IOException {
  388. loopDetector.claim(batAt);
  389. ByteBuffer fatData = getBlockAt(batAt);
  390. BATBlock bat = BATBlock.createBATBlock(bigBlockSize, fatData);
  391. bat.setOurBlockIndex(batAt);
  392. _bat_blocks.add(bat);
  393. }
  394. private BATBlock createBAT(int offset, boolean isBAT) throws IOException {
  395. // Create a new BATBlock
  396. BATBlock newBAT = BATBlock.createEmptyBATBlock(bigBlockSize, !isBAT);
  397. newBAT.setOurBlockIndex(offset);
  398. // Ensure there's a spot in the file for it
  399. ByteBuffer buffer = ByteBuffer.allocate(bigBlockSize.getBigBlockSize());
  400. // Header isn't in BATs
  401. long writeTo = Math.multiplyExact(1L + offset, (long)bigBlockSize.getBigBlockSize());
  402. _data.write(buffer, writeTo);
  403. // All done
  404. return newBAT;
  405. }
  406. /**
  407. * Load the block at the given offset.
  408. */
  409. @Override
  410. protected ByteBuffer getBlockAt(final int offset) throws IOException {
  411. // The header block doesn't count, so add one
  412. long blockWanted = offset + 1L;
  413. long startAt = blockWanted * bigBlockSize.getBigBlockSize();
  414. try {
  415. return _data.read(bigBlockSize.getBigBlockSize(), startAt);
  416. } catch (IndexOutOfBoundsException e) {
  417. IndexOutOfBoundsException wrapped = new IndexOutOfBoundsException("Block " + offset + " not found");
  418. wrapped.initCause(e);
  419. throw wrapped;
  420. }
  421. }
  422. /**
  423. * Load the block at the given offset,
  424. * extending the file if needed
  425. */
  426. @Override
  427. protected ByteBuffer createBlockIfNeeded(final int offset) throws IOException {
  428. try {
  429. return getBlockAt(offset);
  430. } catch (IndexOutOfBoundsException e) {
  431. // The header block doesn't count, so add one
  432. long startAt = (offset + 1L) * bigBlockSize.getBigBlockSize();
  433. // Allocate and write
  434. ByteBuffer buffer = ByteBuffer.allocate(getBigBlockSize());
  435. _data.write(buffer, startAt);
  436. // Retrieve the properly backed block
  437. return getBlockAt(offset);
  438. }
  439. }
  440. /**
  441. * Returns the BATBlock that handles the specified offset,
  442. * and the relative index within it
  443. */
  444. @Override
  445. protected BATBlockAndIndex getBATBlockAndIndex(final int offset) {
  446. return BATBlock.getBATBlockAndIndex(
  447. offset, _header, _bat_blocks
  448. );
  449. }
  450. /**
  451. * Works out what block follows the specified one.
  452. */
  453. @Override
  454. protected int getNextBlock(final int offset) {
  455. BATBlockAndIndex bai = getBATBlockAndIndex(offset);
  456. return bai.getBlock().getValueAt(bai.getIndex());
  457. }
  458. /**
  459. * Changes the record of what block follows the specified one.
  460. */
  461. @Override
  462. protected void setNextBlock(final int offset, final int nextBlock) {
  463. BATBlockAndIndex bai = getBATBlockAndIndex(offset);
  464. bai.getBlock().setValueAt(
  465. bai.getIndex(), nextBlock
  466. );
  467. }
  468. /**
  469. * Finds a free block, and returns its offset.
  470. * This method will extend the file if needed, and if doing
  471. * so, allocate new FAT blocks to address the extra space.
  472. */
  473. @Override
  474. protected int getFreeBlock() throws IOException {
  475. int numSectors = bigBlockSize.getBATEntriesPerBlock();
  476. // First up, do we have any spare ones?
  477. int offset = 0;
  478. for (BATBlock bat : _bat_blocks) {
  479. if (bat.hasFreeSectors()) {
  480. // Claim one of them and return it
  481. for (int j = 0; j < numSectors; j++) {
  482. int batValue = bat.getValueAt(j);
  483. if (batValue == POIFSConstants.UNUSED_BLOCK) {
  484. // Bingo
  485. return offset + j;
  486. }
  487. }
  488. }
  489. // Move onto the next BAT
  490. offset += numSectors;
  491. }
  492. // If we get here, then there aren't any free sectors
  493. // in any of the BATs, so we need another BAT
  494. BATBlock bat = createBAT(offset, true);
  495. bat.setValueAt(0, POIFSConstants.FAT_SECTOR_BLOCK);
  496. _bat_blocks.add(bat);
  497. // Now store a reference to the BAT in the required place
  498. if (_header.getBATCount() >= 109) {
  499. // Needs to come from an XBAT
  500. BATBlock xbat = null;
  501. for (BATBlock x : _xbat_blocks) {
  502. if (x.hasFreeSectors()) {
  503. xbat = x;
  504. break;
  505. }
  506. }
  507. if (xbat == null) {
  508. // Oh joy, we need a new XBAT too...
  509. xbat = createBAT(offset + 1, false);
  510. // Allocate our new BAT as the first block in the XBAT
  511. xbat.setValueAt(0, offset);
  512. // And allocate the XBAT in the BAT
  513. bat.setValueAt(1, POIFSConstants.DIFAT_SECTOR_BLOCK);
  514. // Will go one place higher as XBAT added in
  515. offset++;
  516. // Chain it
  517. if (_xbat_blocks.isEmpty()) {
  518. _header.setXBATStart(offset);
  519. } else {
  520. _xbat_blocks.get(_xbat_blocks.size() - 1).setValueAt(
  521. bigBlockSize.getXBATEntriesPerBlock(), offset
  522. );
  523. }
  524. _xbat_blocks.add(xbat);
  525. _header.setXBATCount(_xbat_blocks.size());
  526. } else {
  527. // Allocate our BAT in the existing XBAT with space
  528. for (int i = 0; i < bigBlockSize.getXBATEntriesPerBlock(); i++) {
  529. if (xbat.getValueAt(i) == POIFSConstants.UNUSED_BLOCK) {
  530. xbat.setValueAt(i, offset);
  531. break;
  532. }
  533. }
  534. }
  535. } else {
  536. // Store us in the header
  537. int[] newBATs = new int[_header.getBATCount() + 1];
  538. System.arraycopy(_header.getBATArray(), 0, newBATs, 0, newBATs.length - 1);
  539. newBATs[newBATs.length - 1] = offset;
  540. _header.setBATArray(newBATs);
  541. }
  542. _header.setBATCount(_bat_blocks.size());
  543. // The current offset stores us, but the next one is free
  544. return offset + 1;
  545. }
  546. protected long size() throws IOException {
  547. return _data.size();
  548. }
  549. @Override
  550. protected ChainLoopDetector getChainLoopDetector() throws IOException {
  551. return new ChainLoopDetector(_data.size());
  552. }
  553. /**
  554. * For unit testing only! Returns the underlying
  555. * properties table
  556. */
  557. PropertyTable _get_property_table() {
  558. return _property_table;
  559. }
  560. /**
  561. * Returns the MiniStore, which performs a similar low
  562. * level function to this, except for the small blocks.
  563. */
  564. POIFSMiniStore getMiniStore() {
  565. return _mini_store;
  566. }
  567. /**
  568. * add a new POIFSDocument to the FileSytem
  569. *
  570. * @param document the POIFSDocument being added
  571. */
  572. void addDocument(final POIFSDocument document) {
  573. _property_table.addProperty(document.getDocumentProperty());
  574. }
  575. /**
  576. * add a new DirectoryProperty to the FileSystem
  577. *
  578. * @param directory the DirectoryProperty being added
  579. */
  580. void addDirectory(final DirectoryProperty directory) {
  581. _property_table.addProperty(directory);
  582. }
  583. /**
  584. * Create a new document to be added to the root directory
  585. *
  586. * @param stream the InputStream from which the document's data
  587. * will be obtained
  588. * @param name the name of the new POIFSDocument
  589. * @return the new DocumentEntry
  590. * @throws IOException on error creating the new POIFSDocument
  591. */
  592. public DocumentEntry createDocument(final InputStream stream,
  593. final String name)
  594. throws IOException {
  595. return getRoot().createDocument(name, stream);
  596. }
  597. /**
  598. * create a new DocumentEntry in the root entry; the data will be
  599. * provided later
  600. *
  601. * @param name the name of the new DocumentEntry
  602. * @param size the size of the new DocumentEntry
  603. * @param writer the writer of the new DocumentEntry
  604. * @return the new DocumentEntry
  605. * @throws IOException if the writer exceeds the given size
  606. */
  607. public DocumentEntry createDocument(final String name, final int size, final POIFSWriterListener writer)
  608. throws IOException {
  609. return getRoot().createDocument(name, size, writer);
  610. }
  611. /**
  612. * create a new DirectoryEntry in the root directory
  613. *
  614. * @param name the name of the new DirectoryEntry
  615. * @return the new DirectoryEntry
  616. * @throws IOException on name duplication
  617. */
  618. public DirectoryEntry createDirectory(final String name)
  619. throws IOException {
  620. return getRoot().createDirectory(name);
  621. }
  622. /**
  623. * Set the contents of a document in the root directory,
  624. * creating if needed, otherwise updating
  625. *
  626. * @param stream the InputStream from which the document's data
  627. * will be obtained
  628. * @param name the name of the new or existing POIFSDocument
  629. * @return the new or updated DocumentEntry
  630. * @throws IOException on error populating the POIFSDocument
  631. */
  632. @SuppressWarnings("UnusedReturnValue")
  633. public DocumentEntry createOrUpdateDocument(final InputStream stream, final String name)
  634. throws IOException {
  635. return getRoot().createOrUpdateDocument(name, stream);
  636. }
  637. /**
  638. * Does the filesystem support an in-place write via
  639. * {@link #writeFilesystem()} ? If false, only writing out to
  640. * a brand new file via {@link #writeFilesystem(OutputStream)}
  641. * is supported.
  642. */
  643. public boolean isInPlaceWriteable() {
  644. return (_data instanceof FileBackedDataSource) && ((FileBackedDataSource) _data).isWriteable();
  645. }
  646. /**
  647. * Write the filesystem out to the open file. Will thrown an
  648. * {@link IllegalArgumentException} if opened from an
  649. * {@link InputStream}.
  650. *
  651. * @throws IOException thrown on errors writing to the stream
  652. */
  653. public void writeFilesystem() throws IOException {
  654. if (!(_data instanceof FileBackedDataSource)) {
  655. throw new IllegalArgumentException(
  656. "POIFS opened from an inputstream, so writeFilesystem() may " +
  657. "not be called. Use writeFilesystem(OutputStream) instead"
  658. );
  659. }
  660. if (!((FileBackedDataSource) _data).isWriteable()) {
  661. throw new IllegalArgumentException(
  662. "POIFS opened in read only mode, so writeFilesystem() may " +
  663. "not be called. Open the FileSystem in read-write mode first"
  664. );
  665. }
  666. syncWithDataSource();
  667. }
  668. /**
  669. * Write the filesystem out
  670. *
  671. * @param stream the OutputStream to which the filesystem will be
  672. * written
  673. * @throws IOException thrown on errors writing to the stream
  674. */
  675. public void writeFilesystem(final OutputStream stream) throws IOException {
  676. // Have the datasource updated
  677. syncWithDataSource();
  678. // Now copy the contents to the stream
  679. _data.copyTo(stream);
  680. }
  681. /**
  682. * Has our in-memory objects write their state
  683. * to their backing blocks
  684. */
  685. private void syncWithDataSource() throws IOException {
  686. // Mini Stream + SBATs first, as mini-stream details have
  687. // to be stored in the Root Property
  688. _mini_store.syncWithDataSource();
  689. // Properties
  690. POIFSStream propStream = new POIFSStream(this, _header.getPropertyStart());
  691. _property_table.preWrite();
  692. _property_table.write(propStream);
  693. // _header.setPropertyStart has been updated on write ...
  694. // HeaderBlock
  695. UnsynchronizedByteArrayOutputStream baos = new UnsynchronizedByteArrayOutputStream(
  696. _header.getBigBlockSize().getBigBlockSize()
  697. );
  698. _header.writeData(baos);
  699. getBlockAt(-1).put(baos.toByteArray());
  700. // BATs
  701. for (BATBlock bat : _bat_blocks) {
  702. ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
  703. bat.writeData(block);
  704. }
  705. // XBats
  706. for (BATBlock bat : _xbat_blocks) {
  707. ByteBuffer block = getBlockAt(bat.getOurBlockIndex());
  708. bat.writeData(block);
  709. }
  710. }
  711. /**
  712. * Closes the FileSystem, freeing any underlying files, streams
  713. * and buffers. After this, you will be unable to read or
  714. * write from the FileSystem.
  715. */
  716. @Override
  717. public void close() throws IOException {
  718. _data.close();
  719. }
  720. /**
  721. * read in a file and write it back out again
  722. *
  723. * @param args names of the files; arg[ 0 ] is the input file,
  724. * arg[ 1 ] is the output file
  725. */
  726. public static void main(String[] args) throws IOException {
  727. if (args.length != 2) {
  728. System.err.println(
  729. "two arguments required: input filename and output filename");
  730. System.exit(1);
  731. }
  732. try (FileInputStream istream = new FileInputStream(args[0])) {
  733. try (FileOutputStream ostream = new FileOutputStream(args[1])) {
  734. try (POIFSFileSystem fs = new POIFSFileSystem(istream)) {
  735. fs.writeFilesystem(ostream);
  736. }
  737. }
  738. }
  739. }
  740. /**
  741. * Get the root entry
  742. *
  743. * @return the root entry
  744. */
  745. public DirectoryNode getRoot() {
  746. if (_root == null) {
  747. _root = new DirectoryNode(_property_table.getRoot(), this, null);
  748. }
  749. return _root;
  750. }
  751. /**
  752. * open a document in the root entry's list of entries
  753. *
  754. * @param documentName the name of the document to be opened
  755. * @return a newly opened DocumentInputStream
  756. * @throws IOException if the document does not exist or the
  757. * name is that of a DirectoryEntry
  758. */
  759. public DocumentInputStream createDocumentInputStream(
  760. final String documentName) throws IOException {
  761. return getRoot().createDocumentInputStream(documentName);
  762. }
  763. /**
  764. * remove an entry
  765. *
  766. * @param entry to be removed
  767. */
  768. void remove(EntryNode entry) throws IOException {
  769. // If it's a document, free the blocks
  770. if (entry instanceof DocumentEntry) {
  771. POIFSDocument doc = new POIFSDocument((DocumentProperty) entry.getProperty(), this);
  772. doc.free();
  773. }
  774. // Now zap it from the properties list
  775. _property_table.removeProperty(entry.getProperty());
  776. }
  777. /* ********** START begin implementation of POIFSViewable ********** */
  778. /**
  779. * Get an array of objects, some of which may implement
  780. * POIFSViewable
  781. *
  782. * @return an array of Object; may not be null, but may be empty
  783. */
  784. @Override
  785. public Object[] getViewableArray() {
  786. if (preferArray()) {
  787. return getRoot().getViewableArray();
  788. }
  789. return new Object[0];
  790. }
  791. /**
  792. * Get an Iterator of objects, some of which may implement
  793. * POIFSViewable
  794. *
  795. * @return an Iterator; may not be null, but may have an empty
  796. * back end store
  797. */
  798. @Override
  799. public Iterator<Object> getViewableIterator() {
  800. if (!preferArray()) {
  801. return getRoot().getViewableIterator();
  802. }
  803. return Collections.emptyIterator();
  804. }
  805. /**
  806. * Give viewers a hint as to whether to call getViewableArray or
  807. * getViewableIterator
  808. *
  809. * @return true if a viewer should call getViewableArray, false if
  810. * a viewer should call getViewableIterator
  811. */
  812. @Override
  813. public boolean preferArray() {
  814. return getRoot().preferArray();
  815. }
  816. /**
  817. * Provides a short description of the object, to be used when a
  818. * POIFSViewable object has not provided its contents.
  819. *
  820. * @return short description
  821. */
  822. @Override
  823. public String getShortDescription() {
  824. return "POIFS FileSystem";
  825. }
  826. /* ********** END begin implementation of POIFSViewable ********** */
  827. /**
  828. * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
  829. */
  830. public int getBigBlockSize() {
  831. return bigBlockSize.getBigBlockSize();
  832. }
  833. /**
  834. * @return The Big Block size, normally 512 bytes, sometimes 4096 bytes
  835. */
  836. @SuppressWarnings("WeakerAccess")
  837. public POIFSBigBlockSize getBigBlockSizeDetails() {
  838. return bigBlockSize;
  839. }
  840. /**
  841. * Creates a new POIFSFileSystem in a new {@link File}.
  842. * Use {@link #POIFSFileSystem(File)} to open an existing File,
  843. * this should only be used to create a new empty filesystem.
  844. *
  845. * @param file The file to create and open
  846. * @return The created and opened POIFSFileSystem
  847. */
  848. public static POIFSFileSystem create(File file) throws IOException {
  849. // Create a new empty POIFS in the file
  850. try (POIFSFileSystem tmp = new POIFSFileSystem();
  851. OutputStream out = new FileOutputStream(file)) {
  852. tmp.writeFilesystem(out);
  853. }
  854. // Open it up again backed by the file
  855. return new POIFSFileSystem(file, false);
  856. }
  857. @Override
  858. protected int getBlockStoreBlockSize() {
  859. return getBigBlockSize();
  860. }
  861. @Internal
  862. public PropertyTable getPropertyTable() {
  863. return _property_table;
  864. }
  865. @Internal
  866. public HeaderBlock getHeaderBlock() {
  867. return _header;
  868. }
  869. @Override
  870. protected void releaseBuffer(ByteBuffer buffer) {
  871. if (_data instanceof FileBackedDataSource) {
  872. ((FileBackedDataSource)_data).releaseBuffer(buffer);
  873. }
  874. }
  875. private static void sanityCheckBlockCount(int block_count) throws IOException {
  876. if (block_count <= 0) {
  877. throw new IOException(
  878. "Illegal block count; minimum count is 1, got " +
  879. block_count + " instead"
  880. );
  881. }
  882. if (block_count > MAX_BLOCK_COUNT) {
  883. throw new IOException(
  884. "Block count " + block_count +
  885. " is too high. POI maximum is " + MAX_BLOCK_COUNT + "."
  886. );
  887. }
  888. }
  889. }