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.

NPOIFSFileSystem.java 32KB

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